实现异步处理的方法很多,经常用的有基于委托的方式,今天记录的是基于事件的异步模式。利用BackgroundWorker组件可以很轻松的实现异步处理,并且该组件还支持事件的取消、进度报告等功能。本文以计算两个数X、Y的和为例。
通过反编译可以看到,这个组件内部也是通过异步委托实现的,报告进度、取消事件等运用了事件技术实现,而事件的本质其实就是委托。
- 程序界面如下图,其中三个文本框分别为两个加数和处理结果,两个按钮为计算和取消,按钮下方为进度条。
- 引入BackgroundWorker组件,为DoWork、ProgressChanged、RunWorkerCompleted三个事件指定方法。
将WorkerReportsProgress属性设为true,以支持报告进度。
将WorkerSupportsCancellation属性设置为true,以支持取消。
public MainForm() { InitializeComponent(); this.backgroundWorker1.DoWork += this.BackgroundWorker1DoWork; this.backgroundWorker1.ProgressChanged += this.BackgroundWorker1ProgressChanged; this.backgroundWorker1.RunWorkerCompleted += this.BackgroundWorker1RunWorkerCompleted; this.backgroundWorker1.WorkerReportsProgress = true; this.backgroundWorker1.WorkerSupportsCancellation = true; this.buttonCancel.Enabled = false; }
- 当点击计算按钮时,调用BackgroundWorker的RunWorkerAsync方法实现异步处理,该方法支持一个object类型的参数。这里用元组Tuple<int,int>传递X、Y的值。调用RunWorkerAsync方法,将触发DoWork事件。
this.backgroundWorker1.RunWorkerAsync(new Tuple<int,int>(Convert.ToInt32(this.textBoxX.Text), Convert.ToInt32(this.textBoxY.Text)));
通过反编译看到以下实现代码:
public void RunWorkerAsync(object argument) { if (this.isRunning) { throw new InvalidOperationException(SR.GetString("BackgroundWorker_WorkerAlreadyRunning")); } this.isRunning = true; this.cancellationPending = false; this.asyncOperation = AsyncOperationManager.CreateOperation(null); this.threadStart.BeginInvoke(argument, null, null); }
可以看到其内部也是通过异步委托实现的,其中threadStart的定义:private delegate void WorkerThreadStartDelegate(object argument);就是一个委托类型。
- 当点击取消按钮时,调用BackgroundWorker的CancelAsync方法,这将改变BackgroundWorker的CancellationPending属性的值为ture。当执行OnWork方法时可以根据CancellationPending属性,判断用户是否要取消事件。
this.backgroundWorker1.CancelAsync();
再看看其内部:
public void CancelAsync() { if (!this.WorkerSupportsCancellation) { throw new InvalidOperationException(SR.GetString("BackgroundWorker_WorkerDoesntSupportCancellation")); } this.cancellationPending = true; }
如上面所说,改变只读属性CancellationPending的值为ture。
- 这里在DoWork方法中
根据CancellationPending属性,判断是否取消。若取消,应在方法结束之前将DoWorkEventArgs的Cancel属性设置为ture。这将用于RunWorkerCompleted事件中判断事件取消还是正常完成。
通过ReportProgress方法报告进度, 调用该方法时传递了一个int类型的参数,该方法将触发ProgressChanged事件。
void BackgroundWorker1DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) { for (int i = 0; i < 10; i++) { System.Threading.Thread.Sleep(500);//模拟耗时操作 if (backgroundWorker1.CancellationPending) { e.Cancel = true return; } else { backgroundWorker1.ReportProgress(i * 10); } } var t = e.Argument as Tuple<int,int>; e.Result = t.Item1 + t.Item2; }
再看报告进度是怎么基于事件实现的:
1.我们调用的ReportProgress方法 public void ReportProgress(int percentProgress) { this.ReportProgress(percentProgress, null); } 2.ReportProgress另一个重载,可以带一个自定义参数,方便传递其他内容,例如一般安装程序中的“正在复制文件”,“正在注册相关组件”提示信息等。 public void ReportProgress(int percentProgress, object userState) { if (!this.WorkerReportsProgress) { throw new InvalidOperationException(SR.GetString("BackgroundWorker_WorkerDoesntReportProgress")); } ProgressChangedEventArgs progressChangedEventArgs = new ProgressChangedEventArgs(percentProgress, userState); if (this.asyncOperation != null) { this.asyncOperation.Post(this.progressReporter, progressChangedEventArgs); return; } this.progressReporter(progressChangedEventArgs); } 3.触发事件 private void ProgressReporter(object arg) { this.OnProgressChanged((ProgressChangedEventArgs)arg); }
- ProgressChanged事件,会将控制权交给UI线程。在其实现方法中根据ProgressChangedEventArgs的ProgressPercentage属性获取进度值。
void BackgroundWorker1ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; }
- OnWork退出后,将调用RunWorkerCompleted事件。
根据RunWorkerCompletedEventArgs的Cancelled属性判断是否正常完成。
根据RunWorkerCompletedEventArgs的Result属性获取处理结果。
void BackgroundWorker1RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) { this.textBoxResult.Text = e.Cancelled ? "Canceled" : e.Result.ToString(); this.buttonCancel.Enabled = false; this.buttonCalculate.Enabled = true; this.progressBar1.Value = 100; }
- 完整代码
1 /* 2 * 由SharpDevelop创建。 3 * 用户: David Huang 4 * 日期: 2015/9/8 5 * 时间: 14:54 6 */ 7 using System; 8 using System.Windows.Forms; 9 10 namespace BackgroundWorkerTest 11 { 12 /// <summary> 13 /// Description of MainForm. 14 /// </summary> 15 public partial class MainForm : Form 16 { 17 public MainForm() 18 { 19 InitializeComponent(); 20 this.backgroundWorker1.DoWork += this.BackgroundWorker1DoWork; 21 this.backgroundWorker1.ProgressChanged += this.BackgroundWorker1ProgressChanged; 22 this.backgroundWorker1.RunWorkerCompleted += this.BackgroundWorker1RunWorkerCompleted; 23 this.backgroundWorker1.WorkerReportsProgress = true; 24 this.backgroundWorker1.WorkerSupportsCancellation = true; 25 this.buttonCancel.Enabled = false; 26 } 27 void ButtonCalculateClick(object sender, EventArgs e) 28 { 29 this.buttonCalculate.Enabled = false; 30 this.textBoxResult.Text = string.Empty; 31 this.buttonCancel.Enabled = true; 32 this.backgroundWorker1.RunWorkerAsync(new Tuple<int,int>(Convert.ToInt32(this.textBoxX.Text), Convert.ToInt32(this.textBoxY.Text))); 33 } 34 void ButtonCancelClick(object sender, EventArgs e) 35 { 36 this.backgroundWorker1.CancelAsync(); 37 } 38 void BackgroundWorker1DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) 39 { 40 for (int i = 0; i < 10; i++) { 41 System.Threading.Thread.Sleep(500); 42 if (backgroundWorker1.CancellationPending) { 43 e.Cancel = true; 44 return; 45 } else { 46 backgroundWorker1.ReportProgress(i * 10); 47 } 48 } 49 var t = e.Argument as Tuple<int,int>; 50 e.Result = t.Item1 + t.Item2; 51 } 52 void BackgroundWorker1ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e) 53 { 54 this.progressBar1.Value = e.ProgressPercentage; 55 } 56 void BackgroundWorker1RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) 57 { 58 this.textBoxResult.Text = e.Cancelled ? "Canceled" : e.Result.ToString(); 59 this.buttonCancel.Enabled = false; 60 this.buttonCalculate.Enabled = true; 61 this.progressBar1.Value = 100; 62 } 63 } 64 }
代码下载:点我