Tuesday, March 1, 2011

Raising Events from a thread safely

I am having some problems with events being raised from the non-UI thread, in that i dont wish to have to handle the If me.invokerequired on every event handler added to the thread in Form1.

I am sure i have read somewhere how to use a delegate event (on SO) but i am unable to find it.

Public Class Form1

    Private WithEvents _to As New ThreadedOperation

    Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button.Click
     _to.start()
    End Sub

    Private Sub _to_SomthingHappend(ByVal result As Integer) Handles _to.SomthingHappend
     TextBox.Text = result.ToString //cross thread exception here
    End Sub

End Class

Public Class ThreadedOperation

    Public Event SomthingHappend(ByVal result As Integer)
    Private _thread As Threading.Thread

    Public Sub start()
     If _thread Is Nothing Then
      _thread = New Threading.Thread(AddressOf Work)
     End If
     _thread.Start()
    End Sub

    Private Sub Work()
     For i As Integer = 0 To 10
      RaiseEvent SomthingHappend(i)
      Threading.Thread.Sleep(500)
     Next
    End Sub

End Class
From stackoverflow
  • You derived your class from Control. A bit unusual, but if the control is actually hosted on a form, you can use Me.Invoke() to marshal the call. For example:

      Private Delegate Sub SomethingHappenedDelegate(ByVal result As Integer)
    
      Private Sub Work()
        For i As Integer = 0 To 10
          Me.Invoke(New SomethingHappenedDelegate(AddressOf SomethingHappenedThreadSafe), i)
          Threading.Thread.Sleep(500)
        Next
      End Sub
    
      Private Sub SomethingHappenedThreadSafe(ByVal result As Integer)
        RaiseEvent SomthingHappend(result)
      End Sub
    

    If this class object is not actually hosted on a form, you'll need to pass a reference to the form so it can call Invoke():

      Private mHost As Form
    
      Public Sub New(ByVal host As Form)
        mHost = host
      End Sub
    

    and use mHost.Invoke(). Or BeginInvoke().

    The last trick in the book is to use your main startup form as the synchronization object. That's not completely safe but works in 99% of the case:

      Dim main As Form = Application.OpenForms(0)
      main.Invoke(New SomethingHappenedDelegate(AddressOf SomethingHappenedThreadSafe), i)
    

    Beware that there's a bug in WF that prevents OpenForms() from accurately tracking open forms when they get dynamically recreated.

    Pondidum : I have gone with the second suggestion, passing the host control (I pass in a control object rather than a form though). thanks
  • If you want to simplify all of this there is a class available called BackgroundWorker that handles the GUI thread marshaling for you.

    Pondidum : Yeah, i know about/use the Background Worker a lot, this component however needs to have greater control over its thread(s)

0 comments:

Post a Comment