Click here to Skip to main content
15,885,141 members
Articles / Programming Languages / Visual Basic

Raising Events from Other Threads

Rate me:
Please Sign up or sign in to vote.
4.17/5 (11 votes)
21 Jun 2008CPOL2 min read 68.6K   555   57   9
A generic function helps to avoid "CrossThreadCall-Exception" when raising events from side-threads

Introduction

In most cases, events are raised to give the owner-object the opportunity to display any kind of changes. "Display any kind of changes" always implicates accessing a control. But when raising an event from a side-thread, we will get that darned "CrossThreadCall-Exception", when trying to display anything. It's simply forbidden to access a control from another thread (the exception tells us). The cross-thread-control-access needs to be "transferred" to an access from main-thread. This is to be done by the Control.Invoke-mechanism. That circumstantially means: create a method, which accesses to the control, create a delegate of it and pass this delegate to a Control.Invoke() - call (nicely done in the article How to solve "Cross thread operation not valid").
But - if my object wants to raise an event - it has no control to which it can pass a delegate!
For that, it can simply use the main form of the application (that's a control too).

Now we can find a general valid solution for any cross-thread-event-raising.
Prerequisite: We have to design our events according to the Framework-conventions for implementing events.
That means, a full specified event consists of 3 parts:

  1. A class "MyEventArgs", inherited from EventArgs
  2. The event-declaration as "EventHandler(Of MyEventArgs)"
  3. An "OnMyEvent(e As MyEventArgs)" - Sub, which raises the event
(If the submitted EventArgs is EventArgs.Empty, then in III there's no need to handle any parameter):
  1. An "OnMyEvent()" - param-free Sub, which raises the event submitting System.EventArgs.Empty

OK, to get this pattern in a generic grip, take a short look at III - OnMyEvent():
It either has the signature of the System.Action - delegate or the signature of the System.Windows.Forms.MethodInvoker - delegate.

Put the parts together to create a "general valid event-invoker":

VB.NET
Public Sub InvokeAction(Of T)( _
      ByVal anAction As System.Action(Of T), _
      ByVal Arg As T, _
      Optional ByVal ThrowMainFormMissingError As Boolean = True)
   If Not ThrowMainFormMissingError AndAlso _
      Application.OpenForms.Count = 0 Then Return
   With Application.OpenForms(0)
      If .InvokeRequired Then                   'if Invoking is required...
         .Invoke(anAction, Arg)  '...pass delegate and argument to Invoke()
      Else                                                 '...otherwise...
         anAction(Arg)                       '...call the delegate directly
      End If
   End With
End Sub

As bonus: With optional passing ThrowMainFormMissingError=False we can suppress the Exception, if there no OpenForm available (e.g. application is closing).
(But normally forget about it.)

The same procedure with the MethodInvoker - delegate:

VB.NET
Public Sub InvokeMethod( _
      ByVal aMethod As System.Windows.Forms.MethodInvoker, _
      Optional ByVal ThrowMainFormMissingError As Boolean = True)
   If Not ThrowMainFormMissingError AndAlso _
      Application.OpenForms.Count = 0 Then Return
   With Application.OpenForms(0)
      If .InvokeRequired Then
         .Invoke(aMethod)
      Else
         aMethod()
      End If
   End With
End Sub

Using the Code

Design your XYEvent according to the Framework usual pattern (only raise it in an "OnXYEvent()" - Sub)
To raise it from a side-thread, call:

VB.NET
InvokeAction(AddressOf OnXYEvent, new XYEventArgs())

instead of:

VB.NET
OnXYEvent(new XYEventArgs())

Code-Sample

I simply show the whole class "CountDown". It contains everything I mentioned:

  • The event "Tick" submits an userdefined EventArgs
  • The event "Finished" submits EventArgs.Empty
  • Both are raised from a side-thread
VB.NET
Imports System.Threading

Public Class CountDown

   Public Class TickEventArgs : Inherits EventArgs
      Public ReadOnly Counter As Integer
      Public Sub New(ByVal Counter As Integer)
         Me.Counter = Counter
      End Sub
   End Class 'TickEventArgs

   Public Event Tick As EventHandler(Of TickEventArgs)

   Protected Overridable Sub OnTick(ByVal e As TickEventArgs)
      RaiseEvent Tick(Me, e)
   End Sub

   Public Event Finished As EventHandler

   Protected Overridable Sub OnFinished()
      RaiseEvent Finished(Me, EventArgs.Empty)
   End Sub

   'System.Threading.Timer calls back from a side-thread
   Private _AsyncTimer As New System.Threading.Timer( _
      AddressOf AsyncTimer_Callback, _
      Nothing, Timeout.Infinite, Timeout.Infinite)

   Private _Counter As Integer

   Public Sub Start(ByVal InitValue As Integer)
      _Counter = InitValue
      _AsyncTimer.Change(0, 1000)          'Execute first callback immediately
   End Sub

   Private Sub AsyncTimer_Callback(ByVal state As Object)
      InvokeAction(AddressOf OnTick, New TickEventArgs(_Counter))  'raise Tick
      'to try a thread-unsafe call, comment out the line before, 
      ' and uncomment the line after
      'OnTick(New TickEventArgs(_Counter))
      If _Counter = 0 Then
         _AsyncTimer.Change(Timeout.Infinite, Timeout.Infinite)    'stop timer
         InvokeMethod(AddressOf OnFinished)                    'raise Finished
      End If
      _Counter -= 1                                       'do a countdowns job
   End Sub

End Class

Points of Interest

  • As you see, the call of InvokeAction() needs no specification of the TypeParameter. It is inferred from the passed parameters.
    The TypeParameter-effect is to enforce, that, if an Action(Of EventArgs) is passed first, the second argument only accepts EventArgs (and no bullsh*t).
  • These invokers are not only useful to transfer event-raisers, but also can transfer any System.Action or MethodInvoker - call to the main-thread.
  • Control.Invoke() is slow. Don't populate a TreeView's hundreds of nodes in a side-thread-raised eventhandler.

(no plagiarism)

I already have published this issue on another VB.NET - platform.

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

 
QuestionYou beautiful genius Pin
Mr.TMG19-Apr-18 11:08
Mr.TMG19-Apr-18 11:08 
AnswerRe: You beautiful genius Pin
Mr.PoorEnglish18-May-18 8:16
Mr.PoorEnglish18-May-18 8:16 
GeneralVery Good Pin
mpemberton17-Nov-11 17:30
mpemberton17-Nov-11 17:30 
GeneralMy vote of 5 Pin
StrannikRus16-Mar-11 10:32
StrannikRus16-Mar-11 10:32 
GeneralAccess to _Counter for Async_Callback Pin
poesboes12-Mar-10 0:44
poesboes12-Mar-10 0:44 
GeneralRe: Access to _Counter for Async_Callback Pin
Mr.PoorEnglish15-Mar-10 3:00
Mr.PoorEnglish15-Mar-10 3:00 
SuggestionRe: Access to _Counter for Async_Callback Pin
My Bones12-Oct-11 22:05
My Bones12-Oct-11 22:05 
GeneralRe: Access to _Counter for Async_Callback Pin
mpemberton17-Nov-11 17:18
mpemberton17-Nov-11 17:18 
Generaluh uh Pin
konikula6-Jan-10 20:32
konikula6-Jan-10 20:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.