Как избежать страшного Application.DoEvents() при многопоточности
Так что я читал много статей, которые не одобряли использование Application.DoEvents() и даже говорили, что его никогда не следует использовать, но я не могу найти хорошую альтернативу для моего сценария... Приложение у которого я работаю, есть метод, который вызывается событием this.Shown при первом запуске основной формы GUI. Метод выполняет некоторую работу, которая занимает около минуты, поэтому тот же метод также создает форму, которая по сути представляет собой пользовательский индикатор выполнения. Имейте в виду, что этот процесс в настоящее время является однопоточным, поэтому, когда этот метод выполняет работу, основной графический интерфейс и индикатор выполнения перестают отвечать на запросы. Если пользователь щелкнет где-нибудь в течение этого времени, экраны погаснут. Поэтому я работаю над тем, чтобы поместить часть работы, которую выполняет этот метод, в поток 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
}
}
Все работает хорошо, за исключением того, что обработка ошибок оказалась сложной и грязной. Есть ли лучший способ работы с потоками при обновлении существующего объекта в основном потоке?
Спасибо за прочтение.
1 ответ
Вы не должны блокировать поток пользовательского интерфейса с помощью этого кода:
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 управляются событиями. Ваш код не использует события для управления выполнением программы.