'1/12/2004 - Daniel Moth creates BackgroundWorker (http://www.danielmoth.com/Blog/)
'4/12/2004 - DDGM: Changed text in comments, and exception messages.
' Made RunWorkerAsync throw if no eventhandler attached
' Added ctor to make the class work on the full framework as is
Namespace System.ComponentModel
#Region "EventArgs classes"
Public Class RunWorkerCompletedEventArgs
Inherits System.EventArgs
' This class should inherit from AsyncCompletedEventArgs but I don't see the point in the CF's case
Private ReadOnly mResult As Object
Private ReadOnly mCancelled As Boolean
Private ReadOnly mError As System.Exception
Public Sub New(ByVal aResult As Object, ByVal aError As System.Exception, ByVal aCancelled As Boolean)
mResult = aResult
mError = aError
mCancelled = aCancelled
End Sub
Public ReadOnly Property Result() As Object
Get
Return mResult
End Get
End Property
Public ReadOnly Property Cancelled() As Boolean
Get
Return mCancelled
End Get
End Property
Public ReadOnly Property [Error]() As System.Exception
Get
Return mError
End Get
End Property
End Class
Public Class ProgressChangedEventArgs
Inherits System.EventArgs
Private ReadOnly mProgressPercent As Int32
Private ReadOnly mUserState As Object
Public Sub New(ByVal aProgressPercent As Int32, ByVal aUserState As Object)
mProgressPercent = aProgressPercent
mUserState = aUserState
End Sub
Public ReadOnly Property ProgressPercentage() As Int32
Get
Return mProgressPercent
End Get
End Property
Public ReadOnly Property UserState() As Object
Get
Return mUserState
End Get
End Property
End Class
Public Class DoWorkEventArgs
Inherits System.ComponentModel.CancelEventArgs
Private ReadOnly mArgument As Object
Private mResult As Object
Public Sub New(ByVal aArgument As Object)
mArgument = aArgument
End Sub
Public ReadOnly Property Argument() As Object
Get
Return mArgument
End Get
End Property
Public Property Result() As Object
Get
Return mResult
End Get
Set(ByVal value As Object)
mResult = value
End Set
End Property
End Class
#End Region
#Region "Delegates for 3 events of class"
Public Delegate Sub DoWorkEventHandler(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Public Delegate Sub ProgressChangedEventHandler(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
Public Delegate Sub RunWorkerCompletedEventHandler(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
#End Region
#Region "BackgroundWorker Class"
'''
''' Executes an operation on a separate thread.
'''
Public Class BackgroundWorker
Inherits System.ComponentModel.Component
#Region "Public Interface"
Public Event DoWork As DoWorkEventHandler
Public Event ProgressChanged As ProgressChangedEventHandler
Public Event RunWorkerCompleted As RunWorkerCompletedEventHandler
'''
''' Initializes a new instance of the BackgroundWorker class.
'''
Public Sub New()
Me.New(New System.Windows.Forms.Control)
' ideally we want to call Control.CreateControl()
' without it, running on the desktop will crash (it is OK on the CF)
' [on the full fx simply calling a control's constructor does not create the Handle.]
' The CreateControl method is not supported on the CF so to keep this assembly retargettable
' I have offered the alternative ctor for desktop clients
' (where they can pass in already created controls)
End Sub
'''
''' Initializes a new instance of the BackgroundWorker class.
''' Call from the desktop code as the other ctor is not good enough
''' Call it passing in a created control e.g. the Form
'''
Public Sub New(ByVal aControl As System.Windows.Forms.Control)
MyBase.New()
mGuiMarshaller = aControl
End Sub
'''
''' Gets a value indicating whether the application has requested cancellation of a background operation.
'''
Public ReadOnly Property CancellationPending() As Boolean
Get
Return mCancelPending
End Get
End Property
'''
''' Raises the BackgroundWorker.ProgressChanged event.
'''
''' The percentage, from 0 to 100, of the background operation that is complete.
Public Sub ReportProgress(ByVal aProgressPercent As Int32)
Me.ReportProgress(aProgressPercent, Nothing)
End Sub
'''
''' Raises the BackgroundWorker.ProgressChanged event.
'''
''' The percentage, from 0 to 100, of the background operation that is complete.
''' The state object passed to BackgroundWorker.RunWorkerAsync(System.Object).
Public Sub ReportProgress(ByVal aProgressPercent As Int32, ByVal aUserState As Object)
If Not mDoesProgress Then
Throw New System.InvalidOperationException("Doesn't do progress events. You must WorkerReportsProgress=True")
End If
' Send the event to the GUI
System.Threading.ThreadPool.QueueUserWorkItem( _
New System.Threading.WaitCallback(AddressOf ProgressHelper), _
New ProgressChangedEventArgs(aProgressPercent, aUserState))
End Sub
'''
''' Starts execution of a background operation.
'''
Public Sub RunWorkerAsync()
Me.RunWorkerAsync(Nothing)
End Sub
'''
''' Starts execution of a background operation.
'''
''' A parameter for use by the background operation to be executed in the BackgroundWorker.DoWork event handler.
Public Sub RunWorkerAsync(ByVal aArgument As Object)
If mInUse Then
Throw New System.InvalidOperationException("Already in use.")
End If
If DoWorkEvent Is Nothing Then
Throw New System.InvalidOperationException("You must subscribe to the DoWork event.")
End If
mInUse = True
mCancelPending = False
System.Threading.ThreadPool.QueueUserWorkItem( _
New System.Threading.WaitCallback(AddressOf DoTheRealWork), aArgument)
End Sub
'''
''' Requests cancellation of a pending background operation.
'''
Public Sub CancelAsync()
If Not mDoesCancel Then
Throw New System.InvalidOperationException("Does not support cancel. You must WorkerSupportsCancellation=True")
End If
mCancelPending = True
End Sub
'''
''' Gets or sets a value indicating whether the BackgroundWorker object can report progress updates.
'''
Public Property WorkerReportsProgress() As Boolean
Get
Return mDoesProgress
End Get
Set(ByVal value As Boolean)
mDoesProgress = value
End Set
End Property
'''
''' Gets or sets a value indicating whether the BackgroundWorker object supports asynchronous cancellation.
'''
Public Property WorkerSupportsCancellation() As Boolean
Get
Return mDoesCancel
End Get
Set(ByVal Value As Boolean)
mDoesCancel = Value
End Set
End Property
#End Region
#Region "Fields"
'Ensures the component is used only once per session
Private mInUse As Boolean
'Stores the cancelation request that the worker thread (user's code) should check via CancellationPending
Private mCancelPending As Boolean
'Whether the object supports cancelling or not (and progress or not)
Private mDoesCancel As Boolean
Private mDoesProgress As Boolean
'Helper objects since Control.Invoke takes no arguments
Private mFinalResult As RunWorkerCompletedEventArgs
Private mProgressArgs As ProgressChangedEventArgs
' Helper for marshalling execution to GUI thread
Private mGuiMarshaller As System.Windows.Forms.Control
#End Region
#Region "Private Methods"
' Async(ThreadPool) called by ReportProgress for reporting progress
Private Sub ProgressHelper(ByVal o As Object)
mProgressArgs = DirectCast(o, ProgressChangedEventArgs)
mGuiMarshaller.Invoke(New System.EventHandler(AddressOf TellThemOnGuiProgress))
End Sub
' ControlInvoked by ProgressHelper for raising progress
Private Sub TellThemOnGuiProgress(ByVal sender As Object, ByVal e As System.EventArgs)
RaiseEvent ProgressChanged(Me, mProgressArgs)
End Sub
' Async(ThreadPool) called by RunWorkerAsync [the little engine of this class]
Private Sub DoTheRealWork(ByVal o As Object)
' declare the vars we will pass back to client on completion
Dim er As System.Exception
Dim ca As Boolean
Dim result As Object
' Raise the event passing the original argument and catching any exceptions
Try
Dim inOut As New DoWorkEventArgs(o)
RaiseEvent DoWork(Me, inOut)
ca = inOut.Cancel
result = inOut.Result
Catch ex As System.Exception
er = ex
End Try
' store the completed final result in a temp var
Dim tempResult As New RunWorkerCompletedEventArgs(result, er, ca)
' return execution to client by going async here
System.Threading.ThreadPool.QueueUserWorkItem( _
New System.Threading.WaitCallback(AddressOf RealWorkHelper), tempResult)
' prepare for next use
mInUse = False
mCancelPending = False
End Sub
' Async(ThreadPool) called by DoTheRealWork [to avoid any rentrancy issues at the client end]
Private Sub RealWorkHelper(ByVal o As Object)
mFinalResult = DirectCast(o, RunWorkerCompletedEventArgs)
mGuiMarshaller.Invoke(New System.EventHandler(AddressOf TellThemOnGuiCompleted))
End Sub
' ControlInvoked by RealWorkHelper for raising final completed event
Private Sub TellThemOnGuiCompleted(ByVal sender As Object, ByVal e As System.EventArgs)
RaiseEvent RunWorkerCompleted(Me, mFinalResult)
End Sub
#End Region
End Class
#End Region
End Namespace