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組件還是相對來說很好用的。

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