有关BackgroundWork类的使用
最近写了个小游戏,想用kinect实现对其的体感控制,所以有关kinect的数据处理就要放在后台线程中,这个可以通过多线程或者并行处理来实现,但是BackgroundWork类可以有事半功倍的效果。下面先介绍下有关该类的知识:
BackgroundWork组件主要是用来实现多线程编程,将程式中复杂的运算放在后台线程中,并实时与前台交互,实现快捷的系统交互体验
BackgroundWork的主要属性,方法,事件如下:
重要属性:
1、CancellationPending 获取一个值,指示应用程序是否已请求取消后台操作。通过在DoWork事件中判断CancellationPending属性可以认定是否需要取消后台操作(也就是结束线程);
2、IsBusy 获取一个值,指示 BackgroundWorker 是否正在运行异步操作。程序中使用IsBusy属性用来确定后台操作是否正在使用中;
3、WorkerReportsProgress 获取或设置一个值,该值指示BackgroundWorker能否报告进度更新
4、WorkerSupportsCancellation 获取或设置一个值,该值指示 BackgroundWorker 是否支持异步取消。设置WorkerSupportsCancellation为true使得程序可以调用CancelAsync方法提交终止挂起的后台 操作的请求;
重要方法:
1、CancelAsync 请求取消挂起的后台操作
2、RunWorkerAsync 开始执行后台操作
3、ReportProgress 引发ProgressChanged事件
重要事件:
1、DoWork 调用 RunWorkerAsync 时发生
2、ProgressChanged 调用 ReportProgress 时发生
3、RunWorkerCompleted 当后台操作已完成、被取消或引发异常时发生
另外还有三个重要的参数是RunWorkerCompletedEventArgs以及DoWorkEventArgs、ProgressChangedEventArgs。
BackgroundWorker的各属性、方法、事件的调用机制和顺序:
从上图可见在整个生活周期内发生了3次重要的参数传递过程:
参数传递1:此次的参数传递是将RunWorkerAsync(Object)中的Object传递到DoWork事件的 DoWorkEventArgs.Argument,由于在这里只有一个参数可以传递,所以在实际应用往封装一个类,将整个实例化的类作为 RunWorkerAsync的Object传递到DoWorkEventArgs.Argument;
参数传递2:此次是将程序运行进度传递给ProgressChanged事件,实际使用中往往使用给方法和事件更新进度条或者日志信息;
参数传递3:在DoWork事件结束之前,将后台线程产生的结果数据赋给DoWorkEventArgs.Result一边在RunWorkerCompleted事件中调用RunWorkerCompletedEventArgs.Result属性取得后台线程产生的结果。
另外从上图可以看到DoWork事件是在后台线程中运行的,所以在该事件中不能够操作用户界面的内容,如果需要更新用户界面,可以使用ProgressChanged事件及RunWorkCompleted事件来实现。
明白了BagkgroundWorker的事件调用顺序和参数传递机制之后在使用该组件用于多线程编程的时候就可以轻松许多了。下面给出一个BackgroundWork多线程的小例子
实现窗体中进度条的刷新,以下是效果图
在界面上拖入backgroundWorker组件,并响应其三个事件。C#代码如下
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace 多线程小例子 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } //这里就是通过响应消息,来处理界面的显示工作 private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; this.label1.Text = e.UserState.ToString(); this.label1.Update(); } //这里是后台工作完成后的消息处理,可以在这里进行后续的处理工作。 private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { MessageBox.Show("运算终于完成了"); } //这里,就是后台进程开始工作时,调用工作函数的地方。你可以把你现有的处理函数写在这儿。 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { work(this.backgroundWorker1); } //真正的处理工作 private bool work(BackgroundWorker bk) { int tatle =10000; for (int i = 0; i < tatle; i++) { if (bk.CancellationPending) //这里判断一下是否用户要求取消后台进行,并可以尽早退出。 { bk.ReportProgress(i, String.Format("当前值是 {0},操作被用户申请中断", i)); return false; } //处理的过程中,通过这个函数,向主线程报告处理进度,最好是折算成百分比,与外边的进度条的最大值必须要对应。这里,我没有折算,而是把界面线程的进度条最大值调整为与这里的总数一致。 bk.ReportProgress(i, String.Format("当前值是 {0} ", i)); } return true; } private void button2_Click(object sender, EventArgs e) { //用户要求取消时,就这样处理一下。有时不太灵喔。 this.backgroundWorker1.CancelAsync(); } private void button1_Click(object sender, EventArgs e) { //这一句,就是让后台工作开始。 this.backgroundWorker1.RunWorkerAsync(); } private void button3_Click(object sender, EventArgs e) { this.Close(); } } }
通过对BackgroundWork的介绍,可以知道它在实现多线程编程上的优势,代码简洁,易于控制;除了该类外我们通常还会用到的方法有:
1、 委托异步调用 将具体耗时的操作作为一个委托,并用BeginInvoke来异步执行这个委托(Invoke是同步调用),并且可以为这个操作传入参数并且通过EndInvoke方法获得返回返回值。
2、使用ThreadPool 新建.net FrameWork中自带的WaitCallback委托,然后放到线程池中运行ThreadPool.QueueUserWorkItem( callback ); 根据WaitCallback委托的定义,可以传入一个object类型的参数。 但是不能精确的控制线程池中的线程。
3、使用Thread 和ThreadPool相比,使用Thread的开销会比较大。但是它有它的优势,使用 Thread 类可以显式管理线程。只要有可能,就应该使用 ThreadPool 类来创建线程。然而,在一些情况下,您还是需要创建并管理您自己的线程,而不是使用 ThreadPool 类。在.net 2.0 中,提供了一个新的委托 ParameterizedThreadStart 支持启动一个线程并传入参数,这是对原来的ThreadStart委托的改进