Как мне избежать ужасного Application.DoEvents() при многопоточности [закрыто]
-
21-12-2019 - |
Вопрос
Итак, я читал много статей, в которых не одобрялось использование Application.DoEvents() и даже говорилось, что его никогда не следует использовать, но я не могу найти хорошей альтернативы для моего сценария...Приложение, над которым я работаю, имеет метод, который вызывается событием this.Shown при первом запуске основной формы графического интерфейса.Метод выполняет некоторую работу, которая занимает около минуты, поэтому тот же метод также создает форму, которая по сути является пользовательским индикатором выполнения.Имейте в виду, что этот процесс в настоящее время является однопоточным, поэтому, когда этот метод выполняет работу, основной графический интерфейс и индикатор выполнения перестают отвечать на запросы.Если пользователь в это время щелкнет где-нибудь, экран погаснет.Поэтому я работаю над тем, чтобы перенести часть работы, которую выполняет этот метод, в поток BackgroundWorker.Вот что я придумал:
private BackgroundWorker Bgw = new BackgroundWorker();
private int LoadPercentage = 0;
//this sub is executed on the main UI thread
public void RunBgw()
{
bool StopThread = false;
//this object should be created in this method and needs to be updated as the child thread is doing work
MyCustomDialog dialog = new MyCustomDialog();
dialog.UpdateProgress(0, "My message");
dialog.Show();
this.Invalidate();
this.Refresh();
//critical properties to set if you want to report progress/be able to cancel the operation
Bgw.WorkerSupportsCancellation = true;
Bgw.WorkerReportsProgress = true;
//add handlers to Bgw so events will fire
Bgw.DoWork += new DoWorkEventHandler(Bgw_DoWork);
Bgw.ProgressChanged += new ProgressChangedEventHandler(Bgw_ProgressChanged);
Bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Bgw_RunWorkerCompleted);
//fire off thread
Bgw.RunWorkerAsync();
while (Bgw.IsBusy == true)
{
if (BW.CancellationPending == true)
{
StopThread = true;
break;
}
Application.DoEvents();
if(LoadPercentage == 10)
{
dialog.UpdateProgress(LoadPercentage, "Still working...");
this.Invalidate();
this.Refresh();
}
if(LoadPercentage == 50)
{
dialog.UpdateProgress(LoadPercentage, "Halfway done...");
this.Invalidate();
this.Refresh();
}
// etc...
//slow down loop so it doesnt take up all the CPU
Thread.Sleep(200);
}
if(!StopThread) {
//continue with something else.
}
}
private void Bgw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker BgwLocal = sender as BackgroundWorker;
if ((BgwLocal.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
TimeConsumingWork();
BgwLocal.ReportProgress(10); //report progress back to the main UI thread
TimeConsumingWork();
BgwLocal.ReportProgress(15, SomeGuiIcon); //pass back specific gui icon
TimeConsumingWork();
BgwLocal.ReportProgress(50);
// etc...
}
}
private void Bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
LoadPercentage = e.ProgressPercentage; //set current percentage of progress
if(LoadPercentage == 15)
{
GuiIcon1 = (Icon)e.UserState; //set gui icon
}
}
private void Bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
//error handling
}
else if (!(e.Error == null))
{
//error handling
}
else
{
//success
}
}
Все работает хорошо, за исключением того, что обработка ошибок оказалась сложной и запутанной.Есть ли лучший способ работы с потоками при обновлении существующего объекта в основном потоке??
Спасибо за прочтение.
Решение
Вам не следует блокировать поток пользовательского интерфейса с помощью этого кода:
while (Bgw.IsBusy == true) { ... }
Вместо этого позвольте RunBgw()
чтобы вернуться к вызывающему абоненту.Используйте события, присутствующие в BackgroundWorker, чтобы узнать, когда он завершился.Конкретно
Bgw.ProgressChanged += new ProgressChangedEventHandler(Bgw_ProgressChanged);
о ходе работы сообщает по телефону
Bgw_ProgressChanged
и
Bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Bgw_RunWorkerCompleted);
причины
Bgw_RunWorkerCompleted
который будет вызван после завершения работы BackgroundWorker.
Обновите индикатор выполнения изнутри Bgw_ProgressChanged
.
Пользовательский интерфейс Windows управляется событиями.Ваш код не использует события для управления выполнением программы.