在VS中利用BackgroundWorker类来实现“仿线程池”

简介:

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 ObjectAs 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 ObjectAs 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(MeNew 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 ObjectByVal 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 ObjectByVal e As RunWorkerCompletedEventArgs)

    SyncLock _ThreadLock

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

      RaiseEvent WorkComplete(MeNew 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(MeNew 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  ObjectAs  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  ObjectByVal E  As System.EventArgs)  Handles _Pool.AllWorkComplete
AddListText( String.Format( "All Work Complete!!!"))
End  Sub

   Private  Sub _Pool_WorkComplete( ByVal Sender  As  ObjectByVal 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  ObjectByVal E  As WorkStartSuspendEventArgs)  Handles _Pool.WorkStart
AddListText( String.Format( "Work {0} is Start", E.ID))
End  Sub

   Private  Sub _Pool_WorkSuspend( ByVal Sender  As  ObjectByVal 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  ObjectAs  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  ObjectByVal 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  IntegerByVal 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  ObjectByVal E  As WorkStartSuspendEventArgs)
Public  Event WorkComplete( ByVal Sender  As  ObjectByVal E  As WorkCompleteEventArgs)
Public  Event WorkSuspend( ByVal Sender  As  ObjectByVal E  As WorkStartSuspendEventArgs)
Public  Event AllWorkComplete( ByVal Sender  As  ObjectByVal 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  ObjectAs  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( MeNew 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( MeNew WorkStartSuspendEventArgs(Work.ID))
End  Sub

   Private  Sub RunWorkerStart( ByVal sender  As  ObjectByVal 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  ObjectByVal e  As RunWorkerCompletedEventArgs)
SyncLock _ThreadLock
Dim T  As clsResult =  CType(e.Result, clsResult)

RaiseEvent WorkComplete( MeNew 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( MeNew 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  IntegerByVal Result  As  Object)
Me.ID = ID
Me.Result = Result
End  Sub
End  Class

    本文转自万仓一黍博客园博客,原文链接: http://www.cnblogs.com/grenet/archive/2011/12/21/2289014.html ,如需转载请自行联系原作者

相关文章
|
1月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
2月前
lua面向对象(类)和lua协同线程与协同函数、Lua文件I/O
Lua的面向对象编程、协同线程与协同函数的概念和使用,以及Lua文件I/O操作的基本方法。
32 4
lua面向对象(类)和lua协同线程与协同函数、Lua文件I/O
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
26 3
|
2月前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
46 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
35 2
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
41 1
|
2月前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
35 0
|
4月前
|
安全 Java 调度
|
4月前
|
安全 Java 程序员
线程安全与 Vector 类的分析
【8月更文挑战第22天】
79 4
|
4月前
|
安全 Java API
Java多线程编程:使用Atomic类实现原子操作
在Java多线程环境中,共享资源的并发访问可能导致数据不一致。传统的同步机制如`synchronized`关键字或显式锁虽能保障数据一致性,但在高并发场景下可能导致线程阻塞和性能下降。为此,Java提供了`java.util.concurrent.atomic`包下的原子类,利用底层硬件的原子操作确保变量更新的原子性,实现无锁线程安全。
35 0