多執行緒初探--使用BackgroundWorker(1)

.Net Framework在多執行緒的支援上提供了許多方便的類別,而BackgroundWorker則是一項非常容易用來撰寫多執行緒的類別, 它不僅和System.Windows.Forms.Timer一樣也在工具箱提供了可拖曳使用的元件,並且提供了ProgressChanged事件使得更動主畫面控制項可以不需藉由Control.Invoke﹝有些時候Invoke的概念對初學者會有或多或少邏輯上的困擾﹞,個人覺得這個元件滿適合初學者當做撰寫多執行緒的入門。

        .Net Framework在多執行緒的支援上提供了許多方便的類別,而BackgroundWorker則是一項非常容易用來撰寫多執行緒的類別, 它不僅和System.Windows.Forms.Timer一樣也在工具箱提供了可拖曳使用的元件,並且提供了ProgressChanged事件使得更動主畫面控制項可以不需藉由Control.Invoke﹝有些時候Invoke的概念對初學者會有或多或少邏輯上的困擾﹞,個人覺得這個元件滿適合初學者當做撰寫多執行緒的入門。

       在MSDN文件庫中是這樣形容BackgroundWorker元件的:﹝引述自 MSDN BackgroundWorker 元件概觀

       BackgroundWorker 元件會提供您在不同於應用程式主要 UI 執行緒的執行緒上,非同步 (「在幕後執行」) 執行耗時作業的能力。若要使用 BackgroundWorker,您只要告訴它要在幕後執行哪種耗時的背景工作方法,然後呼叫 RunWorkerAsync 方法。對執行緒的呼叫會繼續正常執行,直到工作方法非同步執行。當方法完成時,BackgroundWorker 會藉由引發 RunWorkerCompleted 事件來警示呼叫執行緒,此事件會選擇性地包含作業的結果。

       BackgroundWorker的常用屬性:

1. CancellationPending 屬性:﹝Boolean值﹞當BackgroundWorker執行個體呼叫了CancelAsync方法取消背景作業後,這個值會變成true;這個屬性是唯讀的。
2. IsBusy 屬性:﹝Boolean值﹞表示BackgroundWorker執行個體正在執行中;這個屬性是唯讀的。
3. WorkerReportsProgress 屬性 :﹝Boolean值﹞這個值可以設定BackgroundWorker執行個體是否可以藉由呼叫ReprotProgress方法來引發ProgressChanged事件。
4. WorkerSupportsCancellation 屬性 :﹝Boolean值﹞這個值可以設定BackgroundWorker執行個體是否可以藉由呼叫CancelAsync方法來中斷背景作業。

      BackgroundWorker的常用方法:

1. CancelAsync 方法:呼叫這個方法會送出停止背景作業的要求,並將 CancellationPending 屬性設為 true。
2. ReportProgress 方法:呼叫這個方法會引發 ProgressChanged 事件,於是可以將對於主執行緒使用者介面﹝UI﹞控制項的處理程序撰寫於ProgressChanged 事件程序碼中。 如果不熟悉委派與Control.Invoke,以此方法呼叫ProgressChanged是一種不錯的替代方式。
3. RunWorkerAsync 方法 :呼叫這個方法將會引發 DoWork 事件開始執行背景執行緒,需要在背景中執行的程序即可撰寫於DoWork事件程序碼中。這個方法有兩個多載,其中一個多載可以將參數以Object形式傳遞給DoWork事件。

      BackgroundWorker的常用事件:

1. DoWork 事件:將背景執行緒啟動時要執行的程序內容寫在此事件當中。
2. ProgressChanged 事件:在DoWork事件程序中,若有需要更動主畫面控制項時,可以呼叫Reportgress方法來引發此事件,而將控制項更動處理的程序內容寫在此事件中。
3. RunWorkerCompleted 事件:當DoWork作業已完成、取消或引發例外狀況時會引發這個事件。

      先以一最簡單的例子來觀察BackgroundWorker是如何運作的,此程式碼在範例中的Form2。先從工具箱拖曳一個BackgroundWorker元件,並在畫面上布置四個Label物件、一個ProgressBar物件與一個Button物件。為了使得此Backgroundworker可以呼叫ReportProgress方法,故在Form2的Load事件中設定BackgroundWorker1.WorkerReportsProgress = True。接著在Button1的Click事件中撰寫以下程序:

      If BackgroundWorker1.IsBusy = True Then
           MessageBox.Show("背景作業執行中,請稍候!")
       Else
           Label1.Text = "開始於:" & Now().ToString("HH:mm:ss.fffffff")
           BackgroundWorker1.RunWorkerAsync()
           Label2.Text = "Click事件結尾:" & Now().ToString("HH:mm:ss.fffffff")
       End If

     以上這簡單程序的內容表示當按下Button1時先檢查BackgroundWorker1是否正在執行背景作業中,如果為False則在Label1上顯示Button1.Click的時間,接著呼叫RunWorkerAsync方法執行背景作業‧然後在Label2顯示Button1.Click的程序結尾時間。

     在DoWork事件中則撰寫以下程序:

       Dim i As Integer
        For i = 1 To 100
            BackgroundWorker1.ReportProgress(i)   <==呼叫ReportProgress方法以引發ProgressChanged事件,參數為百分比的分子部份。
            System.Threading.Thread.Sleep(10)
        Next

      在ProgressChanged事件中撰寫以下程序:

       ProgressBar1.Value = e.ProgressPercentage  <==依據ReportProgress方法的傳入參數改變Progressbar的Value
       Label3.Text = "進度i=" & e.ProgressPercentage & "/ 時間:" & Now.ToString("HH:mm:ss.fffffff")
<==顯示每一次ProgressBar改變的時間

      在RunWorkerCompletedBKW01事件中撰寫:Label4.Text = "結束於:" & Now().ToString("HH:mm:ss.fffffff")

       整個Form2的程式主要是在彰顯Backgroundworker與主執行緒會在不同步的方式下進行,由左方的圖形可以發現Button1.Click事件在14:53:57.9687500秒就已進行到最後一行﹝和開始於的時間居然還相同,表示中間的過程似乎還小於千萬分之一秒﹞,但整個背景作業依然繼續進行,直到14:53:59.6562500才引發了RunWorkerCompleted事件。

       這個範例的迴圈數因為沒有很多,所以執行的時間也並沒有很長,各位想像一下,如果今天把BackgroundWorker排除,並且將迴圈數放大,以單執行緒的形式來執行這個程式,主畫面就會有一段長時間處於看起來像是已經停止反應的狀態,使用者恐怕很容易誤認程式已經當掉了。所以在遇到這種非不得已要長時工作的程序就非得使用多執行緒的方式來處理會比使用單一執行緒感覺要好一些,而BackkgroundWorker正是提供一個簡單的方式讓初學者容易上手。

這一篇的範例可以在以下連結下載[VB2005]:BackgroundWorkerTest01.rar