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