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

第一篇說明了BackgroundWorker的基本用法,這一篇要談到以下幾個主題:
(1) 不斷循環執行的背景執行緒及如何中斷。
(2) 執行過程的參數傳遞。

       第一篇說明了BackgroundWorker的基本用法,這一篇要談到以下幾個主題: 
       (1) 不斷循環執行的背景執行緒及如何中斷。 
       (2) 執行過程的參數傳遞。

      

 

 

 

        某些狀態我們會想要讓背景執行緒不斷地循環,有一種最簡單的方式當然是在DoWork事件中使用While…End While或是Do ...Loop迴圈,不過這個方式只要對於程式有點基本功的人都應該都很熟悉,而且這樣的做法和BackgroundWorker沒什麼直接的關係,所以要介紹另一種方法來產生不斷循環的背景作業。這個方法其實也非常簡單,就是在RunWorkerCompleted事件中呼叫RunWorkerAsync方法重新引發DoWork事件。例如像以下的範例:

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted

BackgroundWorker1.RunWorkerAsync()

End Sub

     不過這樣不斷地循環的背景執行緒難免可能會有需要中途停下來,因此得要加點其它的東西上去:

     (1) 將BackgroundWorker的WorkerSupportsCancellation屬性設為True,表示此BackgroundWorker物件可以支援取消作業的動作﹝也就是使它可以呼叫CancelAsync方法﹞。

  Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       BackgroundWorker1.WorkerReportsProgress = True
       BackgroundWorker1.WorkerSupportsCancellation = True
   End Sub

      (2) 在某一個特定事件中呼叫BackgroundWorker的CancelAsync方法﹝例如寫在某一個Button物件的Click事件中﹞。

   Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
       BackgroundWorker1.CancelAsync()
   End Sub

      (3) 在DoWork事件的程序中必要的位置建立檢查CancellationPending 屬性值的程序﹝當BackgroundWorker的CancelAsync方法被呼叫後,這個值將會成為True﹞,並在此一屬性值轉為True時將傳入DoWork事件的DoWorkEventArgs的Cancel屬性值設為True。

 Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Dim CycleCount As Integer
        CycleCount = CType(e.Argument, Integer)
        Dim CycleObject As New StateObject
        CycleObject.CycleCount = CycleCount
        Dim i As Integer
        For i = 1 To 100
            CycleObject.i = i
            If BackgroundWorker1.CancellationPending = True Then  <==在此檢查CancellationPending
                e.Cancel = True <==將DoWorkEventArgs.Cancel設為True
                Exit Sub
            Else
                BackgroundWorker1.ReportProgress(i, CycleObject)
                System.Threading.Thread.Sleep(10)
            End If
        Next
        e.Result = CycleObject <==執行完畢將CycleObject傳遞給RunWorkerCompleted的RunWorkerCompletedEventArgs的Result屬性
   End Sub

    
     (4) 在BackgroundWorker的RunWorkerCompleted事件中檢查傳入此事件的RunWorkerCompletedEventArgs的Cancelled屬性值﹝這個值就是由(3)所描述的System.ComponentModel.DoWorkEventArgs的Cancel屬性值傳進來的﹞,當這個值為Fasle則呼叫RunWorkerAsync方法重新引發DoWork事件重新開始一個新的背景執行緒;若為True則不再繼續。

           If Not (e.Error Is Nothing) Then
               MessageBox.Show(e.Error.ToString)
           ElseIf e.Cancelled = True Then <==如果RunWorkerCompletedEventArgs.Cancelled為True則結束
               Label4.Text = "結束於:" & Now.ToString("HH:mm:ss.fffffff")
           Else 
<==如果RunWorkerCompletedEventArgs.Cancelled為False則繼續
               Dim CycleObject As StateObject
               CycleObject = (CType(e.Result, StateObject))
               Dim CycleCount As Integer = 0
               CycleCount = CycleObject.CycleCount + 1
               BackgroundWorker1.RunWorkerAsync(CycleCount)
           End If

       這邊可能會有人有疑問為何在RunWorkerCompleted事件中不是直接檢查BackgroundWorker的CancellationPending屬性值,原因在於當DoWork事件執行完畢進入RunWorkerCompleted事件後,BackgroundWorker的CancellationPending屬性值將會恢復為False,所以不論怎麼檢查它都沒用的。

        第二個要討論的是參數傳遞,在BackgroundWorker常用的方法中有兩個方法提供了可以傳遞狀態物件的多載﹝State Object 有關此狀態物件的介紹可以參閱 [回呼的秘密花園《State Object》] ﹞:

    (1) BackgroundWorker.RunWorkerAsync (Object) 多載方法:此一方法提供傳遞狀態物件到DoWork事件中DoWorkEventArgs.Argument屬性值。

   Dim CycleCount As Integer = 1
    BackgroundWorker1.RunWorkerAsync(CycleCount) 
<=呼叫RunWorkerAsync時將CycleCount變數傳遞給DoWork事件

    (2) BackgroundWorker.ReportProgress (Int32, Object) 多載方法:此一方法提供傳遞狀態物件到ProgressChanged事件中的ProgressChangedEventArgs.UserState屬性值。

  Public Class StateObject <==自訂一個狀態物件的類別
       Public i As Integer
       Public CycleCount As Integer
       Sub New()
           i = 0
           CycleCount = 0
       End Sub
   End Class

    在DoWork事件程序中

  BackgroundWorker1.ReportProgress(i, CycleObject)  <==呼叫ReportProgress時將CycleObject傳遞給ProgressChanged事件

       這一類狀態物件的傳遞目的通常是為了保持變數在不同的執行緒處理時不致於造成混亂的現象﹝回呼的秘密花園中有這種混亂的例子﹞,尤其當背景執行緒要執行的程序中需要先取得主執行緒的某個變數或是畫面上控制項屬性值當作依據的時候,這種方式是非常有用的。

      希望這兩篇能夠讓不熟悉BackgroundWorker的朋友們能很快的上手,此範例可以在以下連結下載[VB2005]:BackgroundWorkerTest02.rar

[推到Plurk]