Click here to Skip to main content
Licence CPOL
First Posted 3 Nov 2007
Views 25,978
Downloads 161
Bookmarked 53 times

Raising Events from Other Threads

By | 21 Jun 2008 | Article
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":

   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:

   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:

InvokeAction(AddressOf OnXYEvent, new XYEventArgs())

instead of:

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
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)

About the Author

Mr.PoorEnglish



Germany Germany

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralVery Good Pinmembermpemberton17:30 17 Nov '11  
GeneralMy vote of 5 PinmemberStrannikRus10:32 16 Mar '11  
GeneralAccess to _Counter for Async_Callback Pinmemberpoesboes0:44 12 Mar '10  
GeneralRe: Access to _Counter for Async_Callback PinmemberMr.PoorEnglish3:00 15 Mar '10  
SuggestionRe: Access to _Counter for Async_Callback PinmemberMy Bones22:05 12 Oct '11  
GeneralRe: Access to _Counter for Async_Callback Pinmembermpemberton17:18 17 Nov '11  
Generaluh uh Pinmemberkonikula20:32 6 Jan '10  

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

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120517.1 | Last Updated 21 Jun 2008
Article Copyright 2007 by Mr.PoorEnglish
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid