Click here to Skip to main content
15,880,891 members
Articles / Desktop Programming / Windows Forms

AsyncWorker - A Typesafe BackgroundWorker (and about Threading in General)

Rate me:
Please Sign up or sign in to vote.
4.76/5 (23 votes)
11 Apr 2010CPOL9 min read 58.3K   1.8K   93  
Generic methods enable typesafe Threading
Imports System.Threading

Namespace System.Windows.Forms
   ''' <summary>
   ''' you can pass Delegates together with suitable arguments to the methods RunAsync(), 
   ''' NotifyGui() or ReportProgress(), and the Delegate will be executed either in a 
   ''' side-thread or in the Gui-thread
   ''' </summary>
   Public Class AsyncWorker

      Private _ReportNext As Integer
      Public ReportInterval As Integer = 300
      Private _CancelRequested As Integer = 0
      Private _CancelNotified As Boolean
      Private _IsRunning As Boolean
      Private _ToggleIsRunningCallback As AsyncCallback = AddressOf ToggleIsRunningCallback

      Public Event IsRunningChanged As EventHandler
      Public ReadOnly Property CancelRequested() As Boolean
         Get
            Return Thread.VolatileRead(_CancelRequested) <> 0
         End Get
      End Property
      Public Sub Cancel()
         Thread.VolatileWrite(_CancelRequested, 1)
      End Sub
      Public ReadOnly Property IsRunning() As Boolean
         Get
            Return _IsRunning
         End Get
      End Property
      Private Sub ToggleIsRunning()
         _IsRunning = Not _IsRunning
         RaiseEvent IsRunningChanged(Me, EventArgs.Empty)
      End Sub
      Private Sub ToggleIsRunningCallback(ByVal ar As IAsyncResult)
         InvokeGui(New Action(AddressOf ToggleIsRunning))
      End Sub

      Private Function TryInvokeGui(ByVal action As [Delegate], ByVal reportProgress As Boolean, _
            ByVal ParamArray args As Object()) As Boolean
         If Thread.VolatileRead(_CancelRequested) <> 0 Then
            If _CancelNotified Then Throw Me.BugException( _
                  "after cancelation you should notify Gui no longer.")
            _CancelNotified = True
            Return False
         End If
         If reportProgress Then
            Dim ticks = Environment.TickCount
            If ticks < _ReportNext Then Return True
            InvokeGui(action, args)
            'set the next reportingtime, aligned to  ReportInterval 
            _ReportNext = ticks + ReportInterval - (ticks - _ReportNext) Mod ReportInterval
         Else
            InvokeGui(action, args)
         End If
         Return True
      End Function
      Private Sub InvokeGui(ByVal action As [Delegate], ByVal ParamArray args As Object())
         Dim target = TryCast(action.Target, Control)
         If Application.OpenForms.Count = 0 OrElse (target IsNot Nothing AndAlso target.IsDisposed) Then
            Cancel()
         Else
            Application.OpenForms(0).BeginInvoke(action, args)
         End If
      End Sub
      ''' <summary>
      ''' executes the Delegate in the Gui-thread, if the last progress-report is more
      ''' than <see cref=" ReportInterval" />ago, and the process isn't canceled
      ''' </summary>
      Public Function ReportProgress(ByVal psAction As Action) As Boolean
         Return TryInvokeGui(psAction, True)
      End Function
      Public Function ReportProgress(Of T1)(ByVal psAction As Action(Of T1), ByVal arg1 As T1) As Boolean
         Return TryInvokeGui(psAction, True, arg1)
      End Function
      Public Function ReportProgress(Of T1, T2)( _
            ByVal psAction As Action(Of T1, T2), ByVal arg1 As T1, ByVal arg2 As T2) As Boolean
         Return TryInvokeGui(psAction, True, arg1, arg2)
      End Function
      Public Function ReportProgress(Of T1, T2, T3)(ByVal psAction As Action(Of T1, T2, T3), _
            ByVal arg1 As T1, ByVal arg2 As T2, ByVal arg3 As T3) As Boolean
         Return TryInvokeGui(psAction, True, arg1, arg2, arg3)
      End Function
      Public Function ReportProgress(Of T1, T2, T3, T4)(ByVal psAction As Action(Of T1, T2, T3, T4), _
            ByVal arg1 As T1, ByVal arg2 As T2, ByVal arg3 As T3, ByVal arg4 As T4) As Boolean
         Return TryInvokeGui(psAction, True, arg1, arg2, arg3, arg4)
      End Function

      ''' <summary>
      ''' executes the Delegate in the Gui-thread, if the process isn't canceled
      ''' </summary>
      Public Function NotifyGui(ByVal syncAction As Action) As Boolean
         Return TryInvokeGui(syncAction, False)
      End Function
      Public Function NotifyGui(Of T1)(ByVal syncAction As Action(Of T1), ByVal arg1 As T1) As Boolean
         Return TryInvokeGui(syncAction, False, arg1)
      End Function
      Public Function NotifyGui(Of T1, T2)( _
            ByVal syncAction As Action(Of T1, T2), ByVal arg1 As T1, ByVal arg2 As T2) As Boolean
         Return TryInvokeGui(syncAction, False, arg1, arg2)
      End Function
      Public Function NotifyGui(Of T1, T2, T3)(ByVal syncAction As Action(Of T1, T2, T3), _
            ByVal arg1 As T1, ByVal arg2 As T2, ByVal arg3 As T3) As Boolean
         Return TryInvokeGui(syncAction, False, arg1, arg2, arg3)
      End Function
      Public Function NotifyGui(Of T1, T2, T3, T4)(ByVal syncAction As Action(Of T1, T2, T3, T4), _
            ByVal arg1 As T1, ByVal arg2 As T2, ByVal arg3 As T3, ByVal arg4 As T4) As Boolean
         Return TryInvokeGui(syncAction, False, arg1, arg2, arg3, arg4)
      End Function

      Private Function GetCallback(ByVal endInvoke As AsyncCallback) As AsyncCallback
         If IsRunning Then Throw Me.BugException("I'm already running")
         _CancelNotified = False
         _CancelRequested = 0
         ToggleIsRunning()
         _ReportNext = Environment.TickCount
         Return DirectCast([Delegate].Combine(endInvoke, _ToggleIsRunningCallback), AsyncCallback)
      End Function

      ''' <summary>executes the Delegate in a thread of the threadpool</summary>
      Public Sub RunAsync(ByVal asyncAction As Action)
         asyncAction.BeginInvoke(GetCallback(AddressOf asyncAction.EndInvoke), Nothing)
      End Sub
      Public Sub RunAsync(Of T)(ByVal asyncAction As Action(Of T), ByVal arg As T)
         asyncAction.BeginInvoke(arg, GetCallback(AddressOf asyncAction.EndInvoke), Nothing)
      End Sub
      Public Sub RunAsync(Of T1, T2)( _
            ByVal asyncAction As Action(Of T1, T2), ByVal arg1 As T1, ByVal arg2 As T2)
         asyncAction.BeginInvoke(arg1, arg2, GetCallback(AddressOf asyncAction.EndInvoke), Nothing)
      End Sub
      Public Sub RunAsync(Of T1, T2, T3)(ByVal asyncAction As Action(Of T1, T2, T3), _
            ByVal arg1 As T1, ByVal arg2 As T2, ByVal arg3 As T3)
         asyncAction.BeginInvoke(arg1, arg2, arg3, GetCallback(AddressOf asyncAction.EndInvoke), Nothing)
      End Sub
      Public Sub RunAsync(Of T1, T2, T3, T4)(ByVal asyncAction As Action(Of T1, T2, T3, T4), _
            ByVal arg1 As T1, ByVal arg2 As T2, ByVal arg3 As T3, ByVal arg4 As T4)
         asyncAction.BeginInvoke(arg1, arg2, arg3, arg4, GetCallback(AddressOf asyncAction.EndInvoke), Nothing)
      End Sub

   End Class

End Namespace

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions