程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> Visual Basic語言 >> VB綜合教程 >> 在VS中利用BackgroundWorker類來實現“仿線程池”

在VS中利用BackgroundWorker類來實現“仿線程池”

編輯:VB綜合教程
 

在VS編程中,一般遇到比較耗時的操作的時候(例如:從網絡上下載文檔,文件的IO操作等),如果采用一般的做法,主線程會一直等待操作完成,會遇到界面假死的問題。故在此情況下,合理的做法是采用異步操作和多線程操作。異步操作可以在另開一個線程執行耗時的操作,在主線程上是不等返回,直接操作下一步,從而解決了界面假死的情況。不過,由於異步操作是新開了一個線程,在新開的線程裡操作界面元素的時候(例如:在下載文檔時顯示進度,修改界面上的進度條的數值),會拋出一個線程安全的異常。為了解決這個問題,VS提供了BackgroundWorker類,通過內部封裝,提供了一個異步操作,同時又能解決線程安全的問題。

BackgroundWorker類提供了二個方法和三個事件來實現異步操作的線程安全的問題。

首先是RunWorkerAsync方法,告訴系統現在要新開一個線程來執行一個異步操作。該方法會引發DoWork事件,在DoWork事件內,執行一個耗時的操作。此時,該事件中的代碼是執行在另一個線程中。如果在該事件中,嘗試操作主界面上的元素的時候,立馬拋出一個線程安全的異常。

那該如何操作主界面的元素呢?在DoWork事件中調用ReportProgress方法,引發ProgressChanged事件,並通過userState參數把參數傳遞過去。Progresschanged事件是和主界面在一個線程裡。在該事件裡,根據傳遞來的參數操作主界面上的元素就不會有線程安全的問題。

在執行完DoWork事件中的代碼後,會調用RunWorkerCompleted事件,通知主線程,異步操作已經完成。同樣該事件也是和主界面在同一個線程裡,也同樣能操作主界面的元素而不會引發線程安全的異常。

如果現在有一個任務是下載200個網頁。該如何操作?一個接著一個下載,利用類可以解決界面假死和線程安全的問題。不過效率也太低了一點。如果利用多線程同時下載200個網頁,那麼可能超過系統的負擔,造成效率的低下。

“線程池”的概念應運而生。在線程池裡准備好一定數量的線程,例如50個線程。以上面的例子,200個下載網頁任務。由於只有50個線程。那麼50個下載網頁任務先執行,剩下的150個下載網頁任務先暫時掛起。等到某一個線程執行完任務後,再執行掛起的任務。直到所有的任務都完成。“線程池”的好處是嚴格控制線程的數量,不給系統造成太大的負擔。

根據“線程池”的思想。自己編寫了一個類。類的全部代碼附在本文的最後。

接下來,闡述一下該類的具體實現。類的名稱為clsWorkPool

首先,clsWorkPool類定義一個委托,該委托來完成“工作”。該類只負責“線程池”的實現與調度,不實現具體的工作。故用委托比較合適。委托的定義如下:

Public Delegate Function WorkDelegate(ByVal Param As Object) As Object

 

類clsWorkPool的構造函數代碼如下

Public Sub New(ByVal ThreadCount As Integer)

    _ThreadCount = ThreadCount

    ReDim _BgWorker(ThreadCount - 1)

    Dim I As Integer

    For I = 0 To ThreadCount - 1

      _BgWorker(I) = New BackgroundWorker

      AddHandler _BgWorker(I).DoWork, AddressOf RunWorkerStart

      AddHandler _BgWorker(I).RunWorkerCompleted, AddressOf RunWorkerCompleted

    Next

    _Work = New Queue(Of clsWork)

    _ID = 0

    _HadComplete=0

    _ThreadLock = New Object

  End Sub

根據傳遞進來的參數_ThreadCount,來創立“線程池”——BackgroundWorker類的數組。該數組內的數量決定了線程池中線程的數量。_Work是一個隊列對象,將暫時不能執行的任務,掛起到隊列中,等到有空閒的線程的時候再執行。_ThreadLock是一個線程安全鎖。防止多線程操作,修改參數,互相影響。

 

類clsWorkPool的添加任務的代碼

  Public Function DoWork(ByVal Work As WorkDelegate, ByVal Param As Object) As Integer

    SyncLock _ThreadLock

      Dim I As Integer, J As Boolean

      _ID += 1

      J = False

      Dim tWork As New clsWork(Work, Param, _ID)

      For I = 0 To _ThreadCount - 1

        If _BgWorker(I).IsBusy = False Then

          RaiseWorkStart(_BgWorker(I), tWork)

          J = True

          Exit For

        End If

      Next

      If J = False Then

        _Work.Enqueue(tWork)

        RaiseEvent WorkSuspend(Me, New WorkStartSuspendEventArgs(_ID))

      End If

      DoWork = _ID

    End SyncLock

  End Function

由於牽涉到多線程異步操作,故在代碼的開始和結束添加線程鎖。首先,根據傳遞進來的參數,生成一個包含任務各種參數的一個類clsWork。然後遍歷線程池,看有沒有空閒的線程。如果有空閒的線程,調用RaiseWorkStart(_BgWorker(I), tWork)方法,通過空閒的線程來完成任務。在RaiseWorkStart(_BgWorker(I), tWork)方法中,有兩句話,一是調用BackgroundWorker類的實例Work的RunWorkerAsync方法,啟用輔助線程完成工作;一是引發WorkStart事件,通知主線程該工作已經啟動。如果沒有空閒的線程,則將該任務添加到隊列_Work中,等待空閒的線程,並引發WorkSuspend事件,通知主線程該工作暫時掛起。

 

在調用Work的RunWorkerAsync方法之後,會引發Work的DoWork的事件,即下面的RunWorkerStart方法,通過調用clsWork類的Work委托的Invoke方法,來完成該任務,並將返回值寫回。

  Private Sub RunWorkerStart(ByVal sender As Object, ByVal e As DoWorkEventArgs)

    Dim T As clsWork = CType(e.Argument, clsWork)

    e.Result = New clsResult(T.ID, T.Work.Invoke(T.Param))

  End Sub

 

在執行完上面的函數,會引發Work的RunWorkerCompleted事件,即下面的RunWorkerCompleted方法。

Private Sub RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)

    SyncLock _ThreadLock

      Dim T As clsResult = CType(e.Result, clsResult)

      RaiseEvent WorkComplete(Me, New WorkCompleteEventArgs(T.ID, T.Result))

      _HadComplete += 1

      If _Work.Count > 0 Then

        Dim tW As BackgroundWorker = CType(sender, BackgroundWorker)

        If tW.IsBusy = False Then RaiseWorkStart(tW, _Work.Dequeue)

      Else

        If _HadComplete >= _ID Then RaiseEvent AllWorkComplete(Me, New EventArgs)

      End If

    End SyncLock

  End Sub

首先引發WorkComplete事件,告訴主線程,該任務已經完成。將完成的任務數加1。同時,檢查掛起的任務數,若還有掛起的任務,則調用RaiseWorkStart方法,重新啟動隊列中的一個新的任務。若沒有掛起的任務,則檢查完成的任務數,任務數達到一定的數量,則說明所有的任務都完成了,則引發AllWorkComplete事件。告知主線程,所有的任務都已經完成。

 

下面舉一個例子,來展示該類的實際效果

在Form上,放一個ListBox和Button。代碼如下

Public Class Form1
  Private WithEvents _Pool As clsWorkPool

  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    _Pool = New clsWorkPool(5)

    Dim I As Integer, J As Integer

    For I = 2008 To 2011
      For J = 1 To 12
        _Pool.DoWork(AddressOf GetWebString, I & "-" & J)
      Next
    Next
  End Sub

  Public Function GetWebString(ByVal Url As Object) As Object
    Dim _Web As New Net.WebClient
    Dim _UrlParam() As String = CType(Url, String).Split("-")
    Dim _Url As String = String.Format("http://www.istartedsomething.com/bingimages/?m={0}&y={1}", _UrlParam(0), _UrlParam(1))

    Dim str As IO.Stream

    str = _Web.OpenRead(_Url)
    Dim read As New IO.StreamReader(str, System.Text.Encoding.GetEncoding("GB2312"))
    Dim Text As String = read.ReadToEnd()

    Return Url
  End Function

  Private Sub AddListText(ByVal Text As String)
    ListBox1.Items.Add(Text)
  End Sub

  Private Sub _Pool_AllWorkComplete(ByVal Sender As Object, ByVal E As System.EventArgs) Handles _Pool.AllWorkComplete
    AddListText(String.Format("All Work Complete!!!"))
  End Sub

  Private Sub _Pool_WorkComplete(ByVal Sender As Object, ByVal E As WorkCompleteEventArgs) Handles _Pool.WorkComplete
    AddListText(String.Format("Work {0} is Complete,The result is {1}", E.ID, E.Result.ToString))
  End Sub

  Private Sub _Pool_WorkStart(ByVal Sender As Object, ByVal E As WorkStartSuspendEventArgs) Handles _Pool.WorkStart
    AddListText(String.Format("Work {0} is Start", E.ID))
  End Sub

  Private Sub _Pool_WorkSuspend(ByVal Sender As Object, ByVal E As WorkStartSuspendEventArgs) Handles _Pool.WorkSuspend
    AddListText(String.Format("Work {0} is Suspend", E.ID))
  End Sub
End Class


 

 

在按下Button1之後,先初始化線程池中5個線程。然後添加了48個下載網頁任務,每個任務調用GetWebString函數,該函數符合Work的委托。由於只有5個線程,故有43個線程被掛起,直到有任務完成後,再執行掛起的任務。

下面,貼二張截圖

 

   

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved