深入剖析BackgroundWorker类

BackgroundWorker是一个在System.ComponentModel命名空间下的帮助类,用于管理工作线程。它提供了以下几个重要的特性:

1)“cancel”标记,可以在不使用Abort的情况下给工作线程打信号终止工作线程(调用CancelAsync方法)。

2)提供报告进度、完成度和退出的标准方案

3)实现了IComponet接口,允许它参与Visual Studio设计器:可以直接从工具箱中拖出而不必写代码进行实例化

4)在工作线程上做异常处理

5)更新Windows Forms 控件以应答工作进度或完成度的能力

其中特性4)和特性5)两条尤为有用,这些使我们不需要再在工作线程中使用try/catch块,并且更新UI控件不需要再调用Control.Invoke了。

实际上BackgroundWorker是使用线程池工作的,这意味着不能在BackgroundWorker线程上调用Abort方法。

好了,说了一大堆概念,下面来看看BackgroundWorker是怎样工作的。

1)首先,调用BackgroundWorker实例(这里假定该实例名为backgroundWorker1)RunWorkerAsync方法来开启后台线程。BackgroundWorker使用十分方便,调用RunWorkerAsync后程序会自动完成接下来的工作直到后台线程完成任务退出,除非用户想取消后台线程或者异常退出。

RunWorkerAsync有两种重载方式:void RunWorkerAsync()和RunWorkerAsync(object argument)。如果不需要给后台线程传入初始数据,使用第一种重载方式就可以了;而当后台线程需要初始数据时就可以使用方式二,不过该重载只接受一个参数,所以如果有多个数据需要传入就要考虑封装成结构或类。

2)调用RunWorkerAsync会触发BackgroundWorker的DoWork事件,RunWorkerAsync(object argument)提供的参数也会传给DoWork的委托方法。DoWork事件的委托方法的形式为【函数名(object senderDoWorkEventArgs e)】,如:

backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)

变量e有两个属性:Argument和Result,RunWorkerAsync传入的参数argument就传给了DoWorkEventArgs eArgument属性;而另一个属性Result则是保存运算结果,二者都是object类型。可以将运算结果赋给Result属性,这个属性会在运算完成后传给OnRunWorkerCompleted事件的委托方法的参数RunWorkerCompletedEventArgs e,从而将运算结果带给前台线程。

DoWork事件的委托方法主要完成后台处理。需要注意的是该委托方法中在处理数据前要判断是否已请求取消后台操作(如调用CancelAsync了方法),如果已经请求取消后台操作,则退出后台线程。判断是否请求取消后台线程的方法为CancellationPending。

                if (backgroundWorker1.CancellationPending)

                {

                    e.Cancel=true;

                    return ;

                }

处理过程中不断发送处理信息,以供前台显示处理进度或完成度的操作也是在这个委托中完成的。发送信息的是BackgroundWorker类的ReportProgress方法[前提是BackgroundWorker的WorkerReportsProgress属性已经设为true,否则调用该方法会引发异常]ReportProgress也有两种重载形式:void ReportProgress(int percentProgress)void ReportProgress(int percentProgress, object userState)

percentProgress返回的是已完成的后台操作所占的百分比,范围从 0% 到 100%由于是百分比,所以最好是一个[0,100]闭合区间的整数,如果超出这个范围则可能引发异常(例如把这个数据赋给进度条控件时)。

关于userState参数,微软给出的说法是:传递到 BackgroundWorker.RunWorkerAsync(System.Object)的状态对象。我感觉是发送后台处理状态给ProgressChanged事件,以供前台判断后续操作,当然也可以把处理过程中的数据通过这个参数传给前台显示。

需要注意的是:在ReportProgress发送信息之前或之后,后台线程应该交出时间片,以供其他线程完成相关操作如主线程UI界面的更新操作等,否则可能会使其他线程无法工作甚至使程序死掉。交出时间片的工作可以使用Thread.Sleep()方法完成,使后台线程暂时休眠一段时间。休眠时间长短可以根据UI线程更新需要的时间设定[如果太短,有可能导致UI线程的更新操作无法完成],如Thread.Sleep(100)。当然如果仅仅是为了交出时间片,也可以设成Thread.Sleep(0)

3)ReportProgress方法会触发BackgroundWorkerProgressChanged事件ProgressChanged事件的委托方法在主线程中运行,主要用于将处理进度等信息反馈给主线程[DoWork事件的委托方法不负责这项工作]ProgressChanged的委托方法形式为:

【方法名(object sender, ProgressChangedEventArgs e)】,如下:

backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)

参数e中就包含着ReportProgress传出的percentProgressuserState[如果第二步调用的是ReportProgress的只有一个参数的重载形式,那么e.UserState为空]。

就这样,BackgroundWorker进行后台运算并和主线程界面进行通信,直到后台任务完成或被中断。

对于一个友好的程序,当后台工作完成后,还应该给用户以提示工作已经完成。这项工作可以通过在BackgroundWorker的OnRunWorkerCompleted事件中添加代码或为其设置委托方法来完成。OnRunWorkerCompleted事件的委托方法形式为【函数名(object sender, RunWorkerCompletedEventArgs e)】。前面说过DoWork事件的委托方法的参数DoWorkEventArgs e中有一个属性Result用于保存运算结果,这个属性的值会在后台运算完成后自动传到OnRunWorkerCompleted事件的委托方法中,从而传给前台线程进行显示。需要注意的是,应该在该方法中判断线程结束的原因[完成后台操作/取消后台操作/发生异常错误],只有成功完成后台操作的情况下才能使用e.Result如果e.Cancel==true或者e.Error!=null就不能使用e.Result,否则会引发“System.InvalidOperationException”类型的异常。

BackgroundWorker还提供了CancelAsync方法允许用户手动终止后台任务,正常使用该方法的前提是WorkerSupportsCancellation属性为true。

至此,关于BackgroundWorker的介绍基本介绍完毕了,希望能给您带来帮助!下面给出一个小例子,展示了BackgroundWorker类的基本使用。

namespace BackgroundWorkerTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            backgroundWorker1.WorkerReportsProgress = true;//报告完成进度
            backgroundWorker1.WorkerSupportsCancellation = true;//允许用户终止后台线程
        }
        //开始按钮
        private void button1_Click(object sender, EventArgs e)
        {
            if (!backgroundWorker1.IsBusy )//判断backgroundWorker1是否正在运行异步操作
            {
                //backgroundWorker1.RunWorkerAsync();
                backgroundWorker1.RunWorkerAsync(1000);//开始执行后台操作
            }
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            e.Result = ListNumber(backgroundWorker1, e);//运算结果保存在e.Result中
        }

        bool ListNumber(object sender, DoWorkEventArgs e)
        {
            int num=(int)e.Argument;//接收传入的参数
            for (int i = 1; i <= num; i++)
            {
                if (backgroundWorker1.CancellationPending)//判断是否请求了取消后台操作
                {
                    e.Cancel=true;
                    return false;
                }
                //backgroundWorker1.ReportProgress(i * 100 / num);
                backgroundWorker1.ReportProgress(i * 100 / num,i);//报告完成进度
                Thread.Sleep(0);//后台线程交出时间片
            }
            return true;
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //将完成进度数据传给进度条
            progressBar1.Value = e.ProgressPercentage;
            label1.Text = e.ProgressPercentage + "%";
            //将中间计算结果在ListBox控件中显示出来
            listBox1.Items.Add(e.UserState);
        }

     Private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
     {
          if (!e.Cancelled && e.Error==null)
          {
              MessageBox.Show("处理完成了吗?  " + e.Result);
          }
          else//如果取消后台线程或发生了异常
          {
              MessageBox.Show("处理完成了吗?  false");
          }
      }

        //取消按钮
        private void button2_Click(object sender, EventArgs e)
        {
            if (backgroundWorker1.WorkerSupportsCancellation==true)
            {
                backgroundWorker1.CancelAsync();//取消后台操作
                backgroundWorker1.Dispose();//释放资源
            }
        }
    }
}

  运行结果如下图:

原文地址:https://www.cnblogs.com/RoyYu/p/2133309.html