C# Ускорить Работу Backgroundworkerа

  • Автор темы Villiya
  • Дата начала
V

Villiya

#1
есть 3 строчки кода которые необходимо выполнить
Код:
 /////данный кусок должен остаться неизменным
CFirDoc p = new CFirDoc(id);			  
double TotalSeconds = p.Go();
Form1.cdoc = p;
////
они являются очень временнозатратными т.е. в самом лучшем случае они расчитываются минут 5, чтобы не висла на это время форма, вынесла их в backgroundWorker следующим образом
Код:
private void CalcBtn_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();

}

public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{

/////данный кусок должен остаться неизменным
CFirDoc p = new CFirDoc(id);			  
double TotalSeconds = p.Go();
Form1.cdoc = p;
////


backgroundWorker1.ReportProgress(i);
}		  
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}

public void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Работа завершена");
}
процесс вроде выполняется, но ОООООЧЕНЬ медленно, может есть возможность его ускорить? может, например, лучше использовать
Код:
var thread = new Thread();
thread.Start();
но как тогда в новом потоке правильно запустить
Код:
CFirDoc p = new CFirDoc(id);			  
double TotalSeconds = p.Go();
Form1.cdoc = p;
и прикрутить к нему прогрессбар?

не отправляйте пожалуйста к литературе, уже было много перечитано, но что-то ничего не помогает
 

LuMee

Well-known member
02.05.2006
477
0
#2
"Ручное" использование Thread вряд ли поможет, ибо BackgroundWorker являет собой, по сути, простую обертку над этим самым Thread'ом.
Полагаю, самым правильным будет оптимизировать сам метод CFirDoc.Go, раз уж он так медленно работает. Рекомендую для начала натравить на него какой нибудь профайлер (я лично предпочитаю EQUATEC, но любой сгодится), чтобы сразу выявить узкие места.
Также интересно, зачем нужен этот фрагмент кода:
Код:
Form1.cdoc = p
Если его убрать, то можно просто запустить 100 потоков (через ThreadPool, чтобы красиво), каждый из которых будет выполнять CFirDoc.Go для своего id. На многоядерной/многопроцессорной машине это может дать выигрыш в производительности.
 
V

Villiya

#3
Также интересно, зачем нужен этот фрагмент кода:
Код:
Form1.cdoc = p
Если его убрать, то можно просто запустить 100 потоков (через ThreadPool, чтобы красиво), каждый из которых будет выполнять CFirDoc.Go для своего id. На многоядерной/многопроцессорной машине это может дать выигрыш в производительности.
Код:
Form1.cdoc = p
используется для передачи данных через экземпляр класса.

нашла вот такое замечание

Использование ThreadPool для длительных вычислений крайне не рекомендуется, потому что количество потоков в пуле ограничено и создаются они небыстро. Поэтому запуск длительных вычислений в ThreadPool может привести к ухудшению производительности.

но если же его все-таки использовать, то каким образом будет выглядеть код? что типа такого?
Код:
private void CalcBtn_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));

}
static void ThreadProc(Object stateInfo) 
{
CFirDoc p = new CFirDoc(id);			  
double TotalSeconds = p.Go();
Form1.cdoc = p;
}
но как тогда в этом случае отобразить ход вычислений на прогрессбаре? и вообще сообщить пользователю что вычисления закончены?
 
V

Villiya

#4
кажется я понимаю почему процесс вычислений так затягивается - я выполняю в цикле один и тот же код 100 раз
Код:
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
/////данный кусок должен остаться неизменным
CFirDoc p = new CFirDoc(id);			  
double TotalSeconds = p.Go();
Form1.cdoc = p;
////
backgroundWorker1.ReportProgress(i);
}		  
}
но если записать например так
Код:
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
backgroundWorker1.ReportProgress(i);
}
/////данный кусок должен остаться неизменным
CFirDoc p = new CFirDoc(id);			  
double TotalSeconds = p.Go();
Form1.cdoc = p;
////		 
}
то прогрессбар быстро заполняется и стоит на месте, в то время как вычмсления проходят сами по себе, тогда такой вопрос: куда правильно вставить код
Код:
/////данный кусок должен остаться неизменным
CFirDoc p = new CFirDoc(id);			  
double TotalSeconds = p.Go();
Form1.cdoc = p;
////
чтобы и он выполнялся нормально в новом потоке и при этом прогрессбар отображал реальный ход вычислений?
 

LuMee

Well-known member
02.05.2006
477
0
#5
Ну если метод Go должен вызываться только один раз, то ситуация в корне меняется: работа по оповещению об изменении прогресса целиком и полностью ложиться на хрупкие плечи CFirDoc. Сделать это можно по аналогии с BackgroundWorker:
1. В классе CFirDoc создаем событие, которое будет вызываться при изменении прогресса:
Код:
public class ProgressEventArgs : EventArgs
{
public int Progress { get; private set; }

public ProgressEventArgs(int progress)
{
this.Progress = progress;
}
}

public class CFirDoc
{
public event EventHandler<ProgressEventArgs> Progress;

private void OnProgress(int progress)
{
var handler = this.Progress;
if (handler != null)
handler.Invoke(new ProgressEventArgs(progress));
}

// Остальной код класса
...
}
2. В теле метода Go в нужных местах втыкаем вызовы OnProgress с указанием текущего прогресса:
Код:
public double Go()
{
// Что-то делаем
...

// Сделано примерно 15% работы
OnProgress(15);

// Еще что-то делаем
...

// Сделано примерно 60% работы
OnProgress(60);

// Доделываем
...

// Доделали
OnProgress(100);
}
3. Перед вызовом метода Go назначаем обработчик события:
Код:
CFirDoc p = new CFirDoc(id);
p.Progress += delegate (object sender, ProgressEventArgs pea)
{
// Отображаем текущий прогресс
};
Без BackgroundWorker'а в этом случае можно, в принципе, и обойтись, используя обычный Thread.
 
V

Villiya

#6
3. Перед вызовом метода Go назначаем обработчик события:
Код:
CFirDoc p = new CFirDoc(id);
p.Progress += delegate (object sender, ProgressEventArgs pea)
{
// Отображаем текущий прогресс
};
Без BackgroundWorker'а в этом случае можно, в принципе, и обойтись, используя обычный Thread.
т.е. достаточно написать в обработчик события на кнопке как-то так?
Код:
 Thread tre = new Thread(new ThreadStart(play));
tre.Start();
tre.Join();
tre = null;
где
Код:
public void play()
{
/////данный кусок должен остаться неизменным
CFirDoc p = new CFirDoc(id);
p.Progress += delegate (object sender, ProgressEventArgs pea)
double TotalSeconds = p.Go();
Form1.cdoc = p;
}
и еще маленький глупый вопрос:
Код:
{
// Отображаем текущий прогресс
};
как это правильно будет записать? в случае использования Thread? и если все-таки использовать backgroundWorker1, то это будет выглядеть так?
Код:
backgroundWorker1.ReportProgress(i);
 
V

Villiya

#7
да и наверно еще один вопрос, думаю не менее глупый: в классе Form1 пишу следующее
Код:
private void CalcBtn_Click(object sender, EventArgs e)
{
Thread NewThread = new Thread(new ThreadStart(RunThread));
NewThread.IsBackground = true;			
// Start the new thread.
NewThread.Start();		  
}


public void RunThread()
{
CFirDoc p = new CFirDoc(id);
p.Progress += delegate (object sender, ProgressEventArgs pea)
{
//какое-то отбражение
}
// запуск расчета на выполнение (возвращает затраченное время в секундах)
double TotalSeconds = p.Go();		  
Form1.cdoc = p;
}
и вроде все нормально, а вот класс CFirDoc - это самостоятельно существующий,отдельный от Form1 класс и если я прописываю
Код:
 namespace ParallelCs
{
public class ProgressEventArgs : EventArgs
{
public int Progress { get; private set; }

public ProgressEventArgs(int progress)
{
this.Progress = progress;
}
}

[Serializable]
public class CFirDoc
{
public event EventHandler<ProgressEventArgs> Progress;

private void OnProgress(int progress)
{
var handler = this.Progress;
if (handler != null)
handler.Invoke(new ProgressEventArgs(progress));
}
//еще какой-то код
}
}
то программа ругается на строчку
Код:
handler.Invoke(new ProgressEventArgs(progress));
и выдается следующая ошибка
Код:
Delegate 'EventHandler' does not take 1 arguments
в чем проблема?
 

LuMee

Well-known member
02.05.2006
477
0
#8
то программа ругается на строчку
Код:
handler.Invoke(new ProgressEventArgs(progress));
Виноват, тут должно быть:
Код:
handler.Invoke(this, new ProgressEventArgs(progress));
EventHandler должен принимать два аргумента: объект, инициировавший событие, и аргументы события.

и еще маленький глупый вопрос:
Код:
{
// Отображаем текущий прогресс
};
как это правильно будет записать? в случае использования Thread? и если все-таки использовать backgroundWorker1, то это будет выглядеть так?
BackgroundWorker тут точно не нужен (зачем плодить лишние сущности?). Делается простой обработчик, устанавливающий текущее значение прогресс-бара, только не надо забывать про InvokeRequired/Invoke:
Код:
private void UpdateProgress(object sender, ProgressEventArgs pea)
{
if (progressBar.InvokeRequired)
progressBar.Invoke(new EventHandler<ProgressEventArgs>(this.Progess), sender, e);
else
progressBar.Value = pea.Progress;
}

...

var p = new CFirDoc();
p.Progress += this.UpdateProgress;
...
 
V

Villiya

#9
Виноват, тут должно быть:
Код:
handler.Invoke(this, new ProgressEventArgs(progress));
EventHandler должен принимать два аргумента: объект, инициировавший событие, и аргументы события.


BackgroundWorker тут точно не нужен (зачем плодить лишние сущности?). Делается простой обработчик, устанавливающий текущее значение прогресс-бара, только не надо забывать про InvokeRequired/Invoke:
Код:
private void UpdateProgress(object sender, ProgressEventArgs pea)
{
if (progressBar.InvokeRequired)
progressBar.Invoke(new EventHandler<ProgressEventArgs>(this.Progess), sender, e);
else
progressBar.Value = pea.Progress;
}

...

var p = new ();
p.Progress += this.UpdateProgress;
...
итого в классе CFirDoc имеется следующий код:
Код:
namespace ParallelCs
{	
public class ProgressEventArgs : EventArgs
{
public int Progress { get; private set; }

public ProgressEventArgs(int progress)
{
this.Progress = progress;
}
}

[Serializable]
public class CFirDoc
{
public event EventHandler<ProgressEventArgs> Progress;

private void OnProgress(int progress)
{
var handler = this.Progress;
if (handler != null)
handler.Invoke(this, new ProgressEventArgs(progress));
}
//еще какой-то код
}
при этом на Form1 имеется следующее:
Код:
private void UpdateProgress(object sender, ProgressEventArgs pea)
{
if (progressBar1.InvokeRequired)
progressBar1.Invoke(new EventHandler<ProgressEventArgs>(this.Progress), sender, e);
else
progressBar1.Value = pea.Progress;
}
//расчет
private void CalcBtn_Click(object sender, EventArgs e)
{			
Thread NewThread = new Thread(new ThreadStart(RunThread));
NewThread.IsBackground = true;			
// Start the new thread.
NewThread.Start();			
}

public void RunThread()
{
CFirDoc p = new CFirDoc(id);
p.Progress += delegate(object sender, ProgressEventArgs pea)
{
p.Progress += this.UpdateProgress;
};
// запуск расчета на выполнение (возвращает затраченное время в секундах)
double TotalSeconds = p.Go();		  
Form1.cdoc = p;
}
теперь проблема в
Код:
 progressBar1.Invoke(new EventHandler<ProgressEventArgs>(this.Progress), sender, e);
выдаются ошибки
Код:
1.'ParallelCs.Form1' does not contain a definition for 'Progress' and no extension method 'Progress' accepting a first argument of type 'DispersionParallelCs.Form1' could be found (are you missing a using directive or an assembly reference?)
2.The name 'e' does not exist in the current context
я что-то делаю не так?
 

LuMee

Well-known member
02.05.2006
477
0
#10
В проблемной строчке надо this.Progress заменить на this.UpdateProgress. Очепятался.
 
V

Villiya

#11
что-то опять ошибки...все в той же строчке
Код:
 progressBar1.Invoke(new EventHandler<ProgressEventArgs>(this.UpdateProgress), sender, e);
пишет что е не существует в данном контексте, может там не е, а что-то другое должно быть? может реа?
 
V

Villiya

#12
исправила как писала выше - все заработало! :lol: ОГРОМНОЕ спасибо LuMee за потраченное время, силы и проявленное терпение!!!!!!!!очень помог! спасибо еще раз!!!