Использование BackgroundWorker для завершения двух методов один за другим WPF/C#
В моей программе есть два метода, выполнение которых занимает некоторое время, около нескольких минут каждый. В то время как эти методы выполняются, я показываю индикатор выполнения в отдельном окне, которое показывает прогресс каждого метода. Мои два метода находятся в статическом служебном классе. Они выглядят следующим образом:
public static class Utility
{
public static bool TimeConsumingMethodOne(object sender)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100);
(sender as BackgroundWorker).ReportProgress(i);
}
return true;
}
public static bool TimeConsumingMethodTwo(object sender)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(50);
(sender as BackgroundWorker).ReportProgress(i);
}
return true;
}
}
Читая подобные вопросы в SO, я узнал, что мне следует использовать BackgroundWorker и использовать RunWorkerCompleted (), чтобы увидеть, когда работник завершает свою работу. Так что в моем основном () я используется BackgroundWorer () и подписывается на метод RunWorkerCompleted (). Моя цель здесь состоит в том, чтобы сначала запустить Timeconsumingmethod () (и отображать прогресс во время выполнения), затем, как только закончите, запустите Timeconsumingmethod() и снова покажите прогресс, и когда это будет завершено, выведите окно сообщения (которое имитирует некоторую другую работу в моей программе). Мой Main () выглядит следующим образом:
public partial class MainWindow : Window
{
public enum MethodType
{
One,
Two
}
private BackgroundWorker worker = null;
private AutoResetEvent _resetEventOne = new AutoResetEvent(false);
private AutoResetEvent _resetEventTwo = new AutoResetEvent(false);
private ProgressBarWindow pbWindowOne = null;
private ProgressBarWindow pbWindowTwo = null;
public MainWindow()
{
InitializeComponent();
}
private void btnRun_Click(object sender, RoutedEventArgs e)
{
RunMethodCallers(sender, MethodType.One);
_resetEventOne.WaitOne();
RunMethodCallers(sender, MethodType.Two);
_resetEventTwo.WaitOne();
MessageBox.Show("COMPLETED!");
}
private void RunMethodCallers(object sender, MethodType type)
{
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
switch (type)
{
case MethodType.One:
worker.DoWork += MethodOneCaller;
worker.ProgressChanged += worker_ProgressChangedOne;
worker.RunWorkerCompleted += worker_RunWorkerCompletedOne;
break;
case MethodType.Two:
worker.DoWork += MethodTwoCaller;
worker.ProgressChanged += worker_ProgressChangedTwo;
worker.RunWorkerCompleted += worker_RunWorkerCompletedTwo;
break;
}
worker.RunWorkerAsync();
}
private void MethodOneCaller(object sender, DoWorkEventArgs e)
{
Dispatcher.Invoke(() =>
{
pbWindowOne = new ProgressBarWindow("Running Method One");
pbWindowOne.Owner = this;
pbWindowOne.Show();
});
Utility.TimeConsumingMethodOne(sender);
}
private void MethodTwoCaller(object sender, DoWorkEventArgs e)
{
Dispatcher.Invoke(() =>
{
pbWindowTwo = new ProgressBarWindow("Running Method Two");
pbWindowTwo.Owner = this;
pbWindowTwo.Show();
});
Utility.TimeConsumingMethodTwo(sender);
}
private void worker_RunWorkerCompletedOne(object sender, RunWorkerCompletedEventArgs e)
{
_resetEventOne.Set();
}
private void worker_RunWorkerCompletedTwo(object sender, RunWorkerCompletedEventArgs e)
{
_resetEventTwo.Set();
}
private void worker_ProgressChangedOne(object sender, ProgressChangedEventArgs e)
{
pbWindowOne.SetProgressUpdate(e.ProgressPercentage);
}
private void worker_ProgressChangedTwo(object sender, ProgressChangedEventArgs e)
{
pbWindowTwo.SetProgressUpdate(e.ProgressPercentage);
}
}
Теперь проблема у меня есть, когда я использую _resetEventOne.WaitOne (); пользовательский интерфейс зависает. Если я уберу этих двоих ... ждет, оба метода выполняются асинхронно, а выполнение продолжается и выводит MessageBox еще до завершения этих двух методов.
Что я делаю не так? Как я могу заставить программу закончить мой первый BackgroundWorker, а затем перейти к следующему, а затем, когда это будет сделано, вывести MessageBox?
2 ответа:
Теперь проблема у меня есть, когда я использую _resetEventOne.WaitOne (); пользовательский интерфейс зависает. Если я удалил эти два ожидания, оба метода выполняются асинхронно, а выполнение продолжается и выводит MessageBox еще до завершения этих двух методов.
Что я делаю не так?
Когда вы вызываете
WaitOne()
, вы блокируете поток пользовательского интерфейса, в результате чего пользовательский интерфейс зависает. Если вы уберете этот вызов, то, конечно, вы начнете работать сразу с обоими работниками.Есть несколько различные способы подхода к вашему вопросу. Один из них заключается в том, чтобы придерживаться как можно ближе к вашей текущей реализации, и просто зафиксировать самый незначительный минимум, чтобы заставить его работать. Для этого вам нужно будет выполнить фактический следующий оператор в обработчике
RunWorkerCompleted
, а не использовать событие для ожидания выполнения обработчика.Это выглядит так:
public partial class MainWindow : Window { public enum MethodType { One, Two } private BackgroundWorker worker = null; private ProgressBarWindow pbWindowOne = null; private ProgressBarWindow pbWindowTwo = null; public MainWindow() { InitializeComponent(); } private void btnRun_Click(object sender, RoutedEventArgs e) { RunMethodCallers(sender, MethodType.One); } private void RunMethodCallers(object sender, MethodType type) { worker = new BackgroundWorker(); worker.WorkerReportsProgress = true; switch (type) { case MethodType.One: worker.DoWork += MethodOneCaller; worker.ProgressChanged += worker_ProgressChangedOne; worker.RunWorkerCompleted += worker_RunWorkerCompletedOne; break; case MethodType.Two: worker.DoWork += MethodTwoCaller; worker.ProgressChanged += worker_ProgressChangedTwo; worker.RunWorkerCompleted += worker_RunWorkerCompletedTwo; break; } worker.RunWorkerAsync(); } private void MethodOneCaller(object sender, DoWorkEventArgs e) { Dispatcher.Invoke(() => { pbWindowOne = new ProgressBarWindow("Running Method One"); pbWindowOne.Owner = this; pbWindowOne.Show(); }); Utility.TimeConsumingMethodOne(sender); } private void MethodTwoCaller(object sender, DoWorkEventArgs e) { Dispatcher.Invoke(() => { pbWindowTwo = new ProgressBarWindow("Running Method Two"); pbWindowTwo.Owner = this; pbWindowTwo.Show(); }); Utility.TimeConsumingMethodTwo(sender); } private void worker_RunWorkerCompletedOne(object sender, RunWorkerCompletedEventArgs e) { RunMethodCallers(sender, MethodType.Two); } private void worker_RunWorkerCompletedTwo(object sender, RunWorkerCompletedEventArgs e) { MessageBox.Show("COMPLETED!"); } private void worker_ProgressChangedOne(object sender, ProgressChangedEventArgs e) { pbWindowOne.SetProgressUpdate(e.ProgressPercentage); } private void worker_ProgressChangedTwo(object sender, ProgressChangedEventArgs e) { pbWindowTwo.SetProgressUpdate(e.ProgressPercentage); } }
Тем не менее,
BackgroundWorker
был сделан устаревшим более новым API на основе задач сasync
иawait
. С некоторыми небольшими изменениями в коде, он может быть адаптированы, чтобы использовать, что новая идиома:public partial class MainWindow : Window { public enum MethodType { One, Two } private ProgressBarWindow pbWindowOne = null; private ProgressBarWindow pbWindowTwo = null; public MainWindow() { InitializeComponent(); } private async void btnRun_Click(object sender, RoutedEventArgs e) { await RunMethodCallers(sender, MethodType.One); await RunMethodCallers(sender, MethodType.Two); MessageBox.Show("COMPLETED!"); } private async Task RunMethodCallers(object sender, MethodType type) { IProgress<int> progress; switch (type) { case MethodType.One: progress = new Progress<int>(i => pbWindowOne.SetProgressUpdate(i)); await Task.Run(() => MethodOneCaller(progress)); break; case MethodType.Two: progress = new Progress<int>(i => pbWindowTwo.SetProgressUpdate(i)); await Task.Run(() => MethodTwoCaller(progress)); break; } } private void MethodOneCaller(IProgress<int> progress) { Dispatcher.Invoke(() => { pbWindowOne = new ProgressBarWindow("Running Method One"); pbWindowOne.Owner = this; pbWindowOne.Show(); }); Utility.TimeConsumingMethodOne(progress); } private void MethodTwoCaller(IProgress<int> progress) { Dispatcher.Invoke(() => { pbWindowTwo = new ProgressBarWindow("Running Method Two"); pbWindowTwo.Owner = this; pbWindowTwo.Show(); }); Utility.TimeConsumingMethodTwo(progress); } }
Для выполнения вышеизложенного требуется также небольшая корректировка класса
Utility
:static class Utility { public static bool TimeConsumingMethodOne(IProgress<int> progress) { for (int i = 1; i <= 100; i++) { Thread.Sleep(100); progress.Report(i); } return true; } public static bool TimeConsumingMethodTwo(IProgress<int> progress) { for (int i = 1; i <= 100; i++) { Thread.Sleep(50); progress.Report(i); } return true; } }
То есть класс
Обратите внимание, что с учетом вышесказанного код стал значительно короче, проще и написан более прямым способом (т. е. связанные операторы теперь находятся друг с другом в одном методе). Приведенный вами пример обязательно упрощен. Это прекрасно, но это означает, что здесь неизвестно, что представляет собой методProgress<T>
занимает место событияBackgroundWorker.ProgressChanged
и методаReportProgress()
.Thread.Sleep()
. На самом деле, во многих случаях такого рода вещи могут быть переработаны далее таким образом, что только длительная работа выполняется асинхронно. Иногда это может еще больше упростить отчет о ходе выполнения, поскольку он может быть выполнен послеawait
каждого отдельного асинхронно выполняемого рабочего компонента. Например, предположим, что работа в цикле либо изначально асинхронна, либо достаточно затратна. что разумно использоватьTask.Run()
для выполнения каждой итерации цикла. С той же целью, что можно представить с помощьюTask.Delay()
:static class Utility { public static async Task<bool> TimeConsumingMethodOne(Action<int> progress) { for (int i = 1; i <= 100; i++) { await Task.Delay(100); progress(i); } return true; } public static async Task<bool> TimeConsumingMethodTwo(Action<int> progress) { for (int i = 1; i <= 100; i++) { await Task.Delay(50); progress(i); } return true; } }
В приведенном выше примере я также не использую
Progress<T>
. Просто простой делегатAction<int>
для вызывающего абонента, чтобы использовать его так, как он хочет.И с этим изменением ваш код окна становится еще проще:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void btnRun_Click(object sender, RoutedEventArgs e) { await MethodOneCaller(); await MethodTwoCaller(); MessageBox.Show("COMPLETED!"); } private async Task MethodOneCaller() { ProgressBarWindow pbWindowOne = new ProgressBarWindow("Running Method One") { Owner = this }; pbWindowOne.Show(); await Utility.TimeConsumingMethodOne(i => pbWindowOne.SetProgressUpdate(i)); } private async Task MethodTwoCaller() { ProgressBarWindow pbWindowTwo = new ProgressBarWindow("Running Method Two") { Owner = this }; pbWindowTwo.Show(); await Utility.TimeConsumingMethodTwo(i => pbWindowTwo.SetProgressUpdate(i)); } }
Конечно, я воспользовался возможностью удалить перечисление
MethodType
и просто вызвать методы напрямую, что еще больше сократило код. Но даже если все, что вы сделали, это избежали использованияDispatcher.Invoke()
, что все еще сильно упрощает код.В дополнение ко всему этому, если бы вы использовали привязку данных для представления состояния выполнения, а не устанавливали значение напрямую, WPF обработал бы вызов перекрестного потока неявно для вас, так что класс
Но это незначительные уточнения по сравнению с удалением отProgress<T>
даже не требуется, даже если вы не можете рефакторировать код классаUtility
, чтобы он сам былasync
.BackgroundWorker
. Я рекомендую делать это, но вкладываете ли вы время в эти дальнейшие усовершенствования, менее важно.
Вариант, который я предпочитаю, - это иметь эти 2 метода в другом потоке и использовать цикл while, чтобы проверить, работает ли поток по-прежнему, и если это задача use.Задержка() НАПР..
private async void BlahBahBlahAsync() { Thread testThread = new Thread(delegate () { }); newThread = new Thread(delegate () { Timeconsuming(); }); newThread.Start(); while (testThread.IsAlive) { await Task.Delay(50); } } private void Timeconsuming() { // stuff that takes a while }