In VS utilized to implement BackgroundWorker class "like thread pool"

Original link: http://www.cnblogs.com/grenet/archive/2011/12/21/2289014.html

In VS programming, generally encountered relatively time-consuming operation time (for example: to download documents, files from the network IO operation, etc.), if a general practice, the main thread will wait operation is completed, the interface will encounter suspended animation The problem. Therefore, in this case, a reasonable approach is to use asynchronous operations and multithreading. Asynchronous operation can open another thread to perform time-consuming operation, on the main thread is unequal to return directly to the next step, so as to solve the case of suspended animation interface. However, due to the asynchronous operation to open a new thread, when the user interface elements in the newly opened thread (for example: Shows the value of the progress bar on the schedule, modify the interface in the download document), throws a thread-safe abnormal. To solve this problem, VS provides BackgroundWorker class, through the inside of the package, provides an asynchronous operation, but it can help solve thread safety issues.

BackgroundWorker class provides two methods and three events to implement asynchronous thread-safe operation problems.

First RunWorkerAsync method, now tell the system to open a new thread to perform an asynchronous operation. This method raises DoWork event, in DoWork within the event, perform a time-consuming operation. At this point, the event code is executed in another thread. If the time in this event, try operating elements on the main interface, immediately throw a thread-safe exception.

Then how to operate the main interface elements of it? In DoWork call event ReportProgress method, triggering ProgressChanged events, and by userState the past parameter passing parameters. Progresschanged event and is the main interface in a thread. In the incident, according to the elements on the operating parameters passed to the main screen there will be thread-safety issues.

In after executing DoWork code after the event, it will call RunWorkerCompleted event, notify the main thread, asynchronous operation has been completed. The main event is the same interface and the same thread, also can operate the main interface elements without causing thread-safe exception.

If there is a task now is to download 200 pages. How to operate? One by one download, using the interface class can solve the problem of suspended animation and thread-safe. But efficiency is too low point. If you use multiple threads simultaneously download 200 pages, it may be more than the burden of the system, resulting in low efficiency.

The concept of "thread pool" came into being. In the thread pool threads are ready to a certain number, for example 50 threads. The example above, 200 downloaded pages task. Since only 50 threads. So 50 downloaded pages to perform the task, and the remaining 150 downloaded pages mission temporarily suspended. Wait until one thread executing the task, and then perform the pending tasks. Until all tasks are completed. Benefits "thread pool" is strictly control the number of threads, not too great a burden to the system.

According to the idea "thread pool". I have written a class. All code attached to the last class of the article.

Next, to elaborate on the specific implementation of this class. Name of the class is clsWorkPool

First, clsWorkPool class defines a delegate that to complete the "work." This class is only responsible for "thread pool" implementation and scheduling, not the achievement of specific work. Therefore, a suitable delegate. The commission defined as follows:

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

 

Class clsWorkPool constructor code is as follows

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

    _ःAdcomplete = 0

    _ThreadLock = New Object

  End Sub

According to the passed parameter _ThreadCount , to the creation of "thread pool" - BackgroundWorker array class. The number in the array determines the number of threads in the thread pool. _Work is a queue object, the task will temporarily not be executed pending in the queue, wait until there is idle time and then thread execution. _ThreadLock is a thread-safe lock. Prevent multithreading, modify the parameters influence each other.

 

Class clsWorkPool adding a task code

  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的时候,此时只有一个辅助线程,和单线程无异,完成48个任务一共耗时88.6秒。线程数为20的时候,效果比较好,完成48个任务一共耗时26.2秒。线程数为50的时候,此时,所有的任务都没有挂起,直接运行。完成这些任务,一共耗时44.1秒,反而不如20个线程的时候,可见,在下载的任务的时候时,线程数不宜过多。

还有一点说明的是,在例子中,48个任务执行的是同一种任务——调用的同一个函数。实际情况是可以调用不同任务——调用不同的函数,只要这些函数满足同一种委托。

 

附:“线程池”的全部代码。代码格式修正于2012年1月6日

Imports System.ComponentModel
Public Class clsWorkPool
   Public Delegate Function WorkDelegate( ByVal Param As Object) As Object

   Private Class clsWork
     Public Work As WorkDelegate
     Public Param As Object
     Public ID As Integer

     Public Sub New( ByVal Work As WorkDelegate, ByVal Param As Object, ByVal ID As Integer)
       Me.Work = Work
       Me.Param = Param
       Me.ID = ID
     End Sub
   End Class

   Private Class clsResult
     Public Result As Object
     Public ID As Integer

     Public Sub New( ByVal ID As Integer, ByVal Result As Object)
       Me.ID = ID
       Me.Result = Result
     End Sub
   End Class

   Private _ThreadCount As Integer
   Private _BgWorker() As BackgroundWorker
   Private _ID As Integer
   Private _HadComplete As Integer
   Private _Work As Queue( Of clsWork)
   Private _ThreadLock As Object

   Public Event WorkStart( ByVal Sender As Object, ByVal E As WorkStartSuspendEventArgs)
   Public Event WorkComplete( ByVal Sender As Object, ByVal E As WorkCompleteEventArgs)
   Public Event WorkSuspend( ByVal Sender As Object, ByVal E As WorkStartSuspendEventArgs)
   Public Event AllWorkComplete( ByVal Sender As Object, ByVal E As EventArgs)

   Public Sub New()
     Me.New(50)
   End Sub

   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

   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

   Private Sub RaiseWorkStart( ByVal Worker As BackgroundWorker, ByVal Work As clsWork)
    Worker.RunWorkerAsync(Work)
     RaiseEvent WorkStart( Me, New WorkStartSuspendEventArgs(Work.ID))
   End Sub

   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

   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
End Class

Public Class WorkStartSuspendEventArgs
   Inherits EventArgs
   Public ID As Integer

   Public Sub New( ByVal ID As Integer)
     Me.ID = ID
   End Sub
End Class

Public Class WorkCompleteEventArgs
   Inherits EventArgs
   Public ID As Integer
   Public Result As Object

   Public Sub New( ByVal ID As Integer, ByVal Result As Object)
     Me.ID = ID
     Me.Result = Result
   End Sub
End Class

转载于:https://www.cnblogs.com/grenet/archive/2011/12/21/2289014.html

Guess you like

Origin blog.csdn.net/weixin_30410999/article/details/94783953