使用BackgroundWorker时,出现ProgressChanged延迟的现象,即我在西面代码的Completed事件中抛出完成时的messageBox,但是因为ProgressChanged的延迟,我的ProgressBar进度条还没满就弹出了对话框,然后progressBar才满……这问题请帮我分析下。
虽说是AsyncOperation.Post()插入消息会延迟,但是ProgressChanged消息是在Completed消息之前插入的啊,至少执行上应该保持队列去执行消息吧????
代码如下:
public partial class BackgroundWorker_Test : Form { private BackgroundWorker worker2 = null; public BackgroundWorker_Test() { InitializeComponent(); worker2 = new BackgroundWorker(); worker2.DoWork += backgroundWorker2_DoWork; worker2.ProgressChanged += backgroundWorker2_ProgressChanged; worker2.RunWorkerCompleted += RunWorkerCompleted; worker2.WorkerSupportsCancellation = true; worker2.WorkerReportsProgress = true; } private void btn_Cancel_Click(object sender, EventArgs e) { if (worker2.IsBusy) { worker2.CancelAsync(); } } private void btn_Start_Click(object sender, EventArgs e) { if (!worker2.IsBusy) { this.progressBar1.Value = 0; worker2.RunWorkerAsync(); } else { MessageBox.Show("正在执行操作,请稍后"); } } private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker2 = sender as BackgroundWorker; int sum = 0; try { for (int i = 1; i <= 100; i++) { if (worker2.CancellationPending) break; Thread.Sleep(20); sum += i; // 进度报告 worker2.ReportProgress(i); } } catch (Exception ex) { throw ex; } if (worker2.CancellationPending) { e.Cancel = true; } else { e.Result = sum; } } private void backgroundWorker2_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; } private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Error != null) { MessageBox.Show(e.Error.Message); } else if (e.Cancelled) { MessageBox.Show("操作已被取消"); } else { MessageBox.Show(String.Format("操作已完成,结果为:{0}", e.Result)); } } }
如图:
这是一个不错的问题。
首先,这个还是不难理解的,因为ReportProgress之后,具体显示进度,是要切换到主线程来做,主线程是订阅了这个事件。所以这肯定是一个异步的,也就是说,DoWork里面ReportProgress后,并不会等待真正地更新完进度。所以这样就可能会出现你提到的问题。
然后,我修改了一下你的代码
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { private BackgroundWorker worker2 = null; public Form1() { InitializeComponent(); worker2 = new BackgroundWorker(); worker2.DoWork += backgroundWorker2_DoWork; worker2.ProgressChanged += backgroundWorker2_ProgressChanged; worker2.RunWorkerCompleted += RunWorkerCompleted; worker2.WorkerSupportsCancellation = true; worker2.WorkerReportsProgress = true; } private void button1_Click(object sender, EventArgs e) { if (!worker2.IsBusy) { this.progressBar1.Value = 0; waitHandler = new AutoResetEvent(false); worker2.RunWorkerAsync(); } else { MessageBox.Show("正在执行操作,请稍后"); } } AutoResetEvent waitHandler = null; private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker2 = sender as BackgroundWorker; int sum = 0; try { for (int i = 1; i <= 100; i++) { if (worker2.CancellationPending) break; Thread.Sleep(20); sum += i; // 进度报告 worker2.ReportProgress(i); waitHandler.WaitOne(); } } catch (Exception ex) { throw ex; } if (worker2.CancellationPending) { e.Cancel = true; } else { e.Result = sum; } } private void backgroundWorker2_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; waitHandler.Set(); } private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Error != null) { MessageBox.Show(e.Error.Message); } else if (e.Cancelled) { MessageBox.Show("操作已被取消"); } else { MessageBox.Show(String.Format("操作已完成,结果为:{0}", e.Result)); } } } }
我用到了AutoResetEvent来控制,就是说强制地要求ReportProgress操作发出后,要等待事件响应回来。这样从逻辑上说,就可以完全保证你所提到的先后顺序问题。
但实际运行起来,我还是发现会有一点点奇怪,就是看起来那个进度条还没有更新完,那个对话框就弹出来了。我这里只是猜想窗体元素重画的时间可能确实比较长,也就是说,其实是更新完了,但用户看到的可能有一点点延时吧。
没有完美地解决这个问题,但我想这个思路可以给你参考的
谢谢这个思路,我这边先留着看下。过两天我写一篇基于事件异步编程的博文,到时文中会提到这个问题,看看有没有别的想法
哈哈,这个问题就先这样解决了,谢谢你的回答。