BackgroundWorker&&ProgressBar的研究

      最近在项目中遇到一个这样的问题,要求点击计算的时候进度条随着计算的进行而改变,直至完成。但是这个地方点击计算按钮是调用的另一个类中的各个方法,无法精确地计算进度的增进情况。为此,颇费脑筋,最终使用BackgroundWorker组件进行实现。

      1.描述

       点击计算按钮的时候,后台大量数据进行计算,同时进度条增进,直至同时完成。

      

      2.BackgroundWorker的属性,方法及原理

      (参考:http://kb.cnblogs.com/a/1611918/)

      BackgroundWorker类中主要用到的有这列属性、方法和事件:
      重要属性:
      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事件来实现。

    3.代码实现及详解

    首先,创建一个BackgroundWorker组件,并设置WorkerSupportsCancellation和WorkerReportsProgress为true;

   其次,我们设定一个常量MaxRecords = 100。点击计算按钮,执行下面的计算过程,RunWorkerAsync方法被调用,自定义参数被传入,触发DoWork事件,事件处理如下。

        private void btnJS_Click(object sender, EventArgs e)
        {
            //业务处理

            string strSQL = "";
            DataTable dt = GetDataTable(strSQL);
            if(dt.Rows.Count > 0)
            {
                if (dt.Rows[0][0].ToString().Trim() == "0")
                {
                    this.btnJS.Enabled = true;
                    //错误提示

                    return;
                }
                else
                {
                    if (this.backgroundWorkerJS.IsBusy)
                    {
                        return;
                    }
                    this.backgroundWorkerJS.RunWorkerAsync(MaxRecords);
                    this.btnJS.Enabled = false;
                }
            }
        }

        private void backgroundWorkerJS_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                e.Result = this.GetData(this.backgroundWorkerJS, e);
            }
            catch (Exception ex)
            {
                //异常处理

                throw;
            }
        }

    调用GetData方法去处理后台计算。

    private int RetrieveData(BackgroundWorker worker, DoWorkEventArgs e)
        {
            //日期
            string[] strArr = { this.cmbNF.SelectedValue.ToString(), this.cmbYF.SelectedValue.ToString() };
            int maxRecords = (int)e.Argument;
            int percent = 0;
            for (int i = 1; i <= maxRecords; i++)
            {
                if (worker.CancellationPending)
                {
                    return i;
                }

                percent = (int)(((double)i / (double)maxRecords) * 100);
                worker.ReportProgress(percent, strArr);
                Thread.Sleep(100);
            }

            return maxRecords;
        }

      通过e.Argument,获得最大数据获取量之后,进行一个for循环,在每次迭代中,如何worker.CancellationPending==true,代表异步操作被显示取消,则直接返回;否则,调用BackgroundWorker的ReportProgress方法。ReportProgress具有两个重载:

      public void ReportProgress(int percentProgress);
      public void ReportProgress(int percentProgress, object userState);

percentProgress代表当前进度,从0-100。userState便于传入一些额外的参数。这里需要传入年份和月份,以便进行相关的计算。所以定制了一个string类型数组。ReportProgress的调用将会导致ProgressChanged事件被触发。这个方法将会处理进度和数据计算。wnform自带了进度框控件ProgressBar,只要对它的属性Value进行赋值,就可以控制它的增长。

        private void backgroundWorkerJS_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            string[] tempArr = (string[])e.UserState;
            this.lblMsg.Text = string.Format("已完成{0}%,请稍候...", e.ProgressPercentage);
            this.progressBarJS.Value = e.ProgressPercentage;
            //复杂计算部分处理

        }

      最后,不管正常与非正常,只要结束,BackgroundWorker的RunWorkerCompleted就会被触发,可以进行一些结束释放资源的处理操作。

        private void backgroundWorkerJS_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                //相关的一些处理
            }
            catch (TargetInvocationException ex)
            {
                //捕获异常
            }
        }

      BackgroundWorker主要是利用各种EventArgs的参数传递来实现异步,比起直接新建一个线程来执行工作要方便简单。

      4.深入讨论

      (参考:http://www.cnblogs.com/inforasc/archive/2009/10/21/1587756.html; 

                  http://www.cnblogs.com/net66/archive/2005/08/03/206132.html

      BackgroundWorker是微软开发出来的一个组件,它的开发原理还是基于线程,委托等方面。异步委托提供以异步方式调用同步方法的能力。当同步调用委托时,Invoke()方法直接对当前线程调用目标方法;当异步调用委托时,CLR将对请求进行排队并立即返回到调用方,将对来自线程池的线程调用该目标方法,提交请求的原始线程继续与目标方法并行执行,该目标方法是对线程池线程运行的。主要用到两个方法:

1)、BeginInvoke()方法

BeginInvoke()方法启动异步调用,它与需要异步执行的方法具有相同的参数。另外,还有两个可选参数:第一个参数是AsyncCallback委托,该委托引用在异步调用完成时要调用的方法;第二个参数是用户定义的对象,该对象可向回调方法传递信息;BeginInvoke立即返回,不等待异步调用完成;BeginInvoke返回IAsyncResult,这个结果可用于监视异步调用的进度;

2)、EndInvoke()方法

EndInvoke()方法检索异步调用的结果;在调用BeginInvoke()方法后,可以随时调用EndInvoke()方法,如果异步调用尚未完成,则EndInvoke()方法将一直阻止调用线程,直到异步调用完成后才允许调用线程执行;EndInvoke()的参数需要异步执行的方法的out和ref参数以及由BeginInvoke()返回的IAsyncResult。

      委托实现上述情况,但是无法同步进行。这样处理的结果就是复杂计算处理完成后,才进行进度框的增长。给用户呈现“假死”的状态,然后是几乎一瞬间进度框完成。

        private Thread fThread;
        private delegate void SetPos(int ipos);

        private void SetTextMessage(int ipos)
        {

            if (this.InvokeRequired)
            {
                this.Invoke(new SetPos(SetTextMessage), new object[] { ipos });
            }
            else
            {
                if (fThread.IsAlive)
                {
                    this.lblMsg.Text = ipos.ToString() + "%";
                    this.progressBarJS.Value = Convert.ToInt32(ipos);
                }

            }
        }

        private void SleepT()
        {
            for (int i = 0; i < 500; i++)
            {
                System.Threading.Thread.Sleep(10);
                SetTextMessage(100 * i / 500);
            }


        }

点击计算按钮时执行

        private void btnJS_Click(object sender, EventArgs e)
        {
            fThread = new Thread(new ThreadStart(SleepT)); 
            fThread.Start();
            //复杂计算过程

            fThread.Abort();
            this.lblMsg.Text = "计算完成100%";
            this.progressBarJS.Value = 100;
        }

由此可以看出BackgroundWorker组件还是相对来说很好用的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章