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。代码如下
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日
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