使用BackgroundWorker完成另一个WPF / C#之后的两个方法

在我的程序中,我有两种方法需要一段时间才能完成,每种方法大约需要几分钟。 在执行这些方法时,我在一个单独的窗口中显示一个进度条,显示每个方法的进度。 我的两个方法是在一个静态的Utility类中。 它们看起来如下:

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()来查看工作人员何时完成其工作。 所以在我的Main()中我使用了BackgroundWorer()并订阅了RunWorkerCompleted()方法。 我的目标是首先运行TimeConsumingMethodOne()(并在运行时显示进度),然后一旦完成,运行TimeConsumingMethodTwo()并再次显示进度,并在完成后输出消息框(模拟我程序中的其他工作) 。 我的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?

现在我遇到的问题是,当我使用_resetEventOne.WaitOne(); 用户界面挂起。 如果我删除了这两个等待,则两个方法都异步运行并且执行继续并在这两个方法完成之前输出MessageBox。

我究竟做错了什么?

当您调用WaitOne() ,您正在阻止UI线程,导致UI挂起。 如果您删除该呼叫,那么当然您可以立即启动两个工作人员。

有几种不同的方法可以解决您的问题。 一个是紧密结合当前的实现,只需修复最小的最小值即可使其工作。 这样做,您需要做的是在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与asyncawait淘汰。 通过对代码进行一些小的更改,它可以适应使用更新的习惯用法:

 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 progress; switch (type) { case MethodType.One: progress = new Progress(i => pbWindowOne.SetProgressUpdate(i)); await Task.Run(() => MethodOneCaller(progress)); break; case MethodType.Two: progress = new Progress(i => pbWindowTwo.SetProgressUpdate(i)); await Task.Run(() => MethodTwoCaller(progress)); break; } } private void MethodOneCaller(IProgress progress) { Dispatcher.Invoke(() => { pbWindowOne = new ProgressBarWindow("Running Method One"); pbWindowOne.Owner = this; pbWindowOne.Show(); }); Utility.TimeConsumingMethodOne(progress); } private void MethodTwoCaller(IProgress 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 progress) { for (int i = 1; i <= 100; i++) { Thread.Sleep(100); progress.Report(i); } return true; } public static bool TimeConsumingMethodTwo(IProgress progress) { for (int i = 1; i <= 100; i++) { Thread.Sleep(50); progress.Report(i); } return true; } } 

也就是说, Progress类取代了BackgroundWorker.ProgressChanged事件和ReportProgress()方法。

请注意,通过上述内容,代码变得更加简单,更简单,并且以更直接的方式编写(即相关语句现在在相同的方法中相互之间)。

您给出的示例必须简化。 这很好,但它确实意味着Thread.Sleep()方法代表什么不知道。 事实上,在许多情况下,这种事情可以进一步重构,只有长时间运行的工作是异步完成的。 这有时可以进一步简化进度报告,因为可以在await每个异步执行的工作组件之后完成。

例如,假设循环中的工作本质上是异步的,或者代价很高,使用Task.Run()来执行每个循环迭代是合理的。 出于相同的目的,可以使用Task.Delay()表示:

 static class Utility { public static async Task TimeConsumingMethodOne(Action progress) { for (int i = 1; i <= 100; i++) { await Task.Delay(100); progress(i); } return true; } public static async Task TimeConsumingMethodTwo(Action progress) { for (int i = 1; i <= 100; i++) { await Task.Delay(50); progress(i); } return true; } } 

在上面,我也不使用Progress 。 只需一个简单的Action委托即可让调用者随意使用。

通过这种更改,您的窗口代码变得更加简单:

 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类如果你不能重构Utility类代码,它本身就是async

但是,与远离BackgroundWorker相比,这些都是微不足道的改进。 我建议这样做,但是你是否将时间投入到那些进一步的改进中并不那么重要。

我更喜欢的一个选项是在不同的线程中使用这两个方法并使用while循环来检查线程是否仍在运行,以及它是否使用Task.Delay()EG。

 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 }