Raised event not behaving properly...

B2Ben

Well-known member
Joined
Aug 17, 2006
Messages
52
Programming Experience
Beginner
OK... so I want to add some data to a listbox on my Form1. I can do it successfully using a button on my form (See Button1 below). However, when one of my objects (NetServer) raises an event, my Form1 handles that event, starts executing some code, but it cannot modify my listbox and I don't know why.

Below is some code from my form, with comments explaining what works and what doesn't. If you'd like to see more code or more information, let me know... this is driving me up the wall here... :)

VB.NET:
Expand Collapse Copy
Public Class Form1

    Friend WithEvents NetServer As New NetServer(22000)  'Initialize NetServer


    Private Sub Client_Connect(ByVal IPAddress As String) Handles NetServer.ClientConnected
        Beep()  'NOTE: This works properly after the event is received
        Me.listbox_clients.Items.Add(IPAddress)  'NOTE: This doesn't happen... Why??
        MsgBox("Form1 Received Client Connected: " & IPAddress)  'NOTE: This doesn't happen... Why??
    End Sub


    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Me.listbox_clients.Items.Add("Test")  'NOTE: This works properly after clicking Button1 on Form1
    End Sub
 
OK... I finally wised up and used a try...catch block to get an exception to shed some more light on the issue. This is the exception I received:

Cross-thread operation not valid: Control 'listbox_clients' accessed from a thread other than the thread it was created on.
The event is raised from an infinite loop running in a separate thread. Below is some code from the object raising the event:

VB.NET:
Expand Collapse Copy
Public Class NetServer

    Private ListenerThread As Thread

    Public Event ClientConnected(ByVal IPAddress As String)


    Sub New(ByVal PortNum As Integer)

        Me.ListenerThread = New Thread(AddressOf DoListen)
        Me.ListenerThread.IsBackground = True
        Me.ListenerThread.Start()  'Start listening for connections

    End Sub

    Private Sub DoListen()
        Try
            Me.Listener = New TcpListener(System.Net.IPAddress.Parse(LocalIP()), Me.PortNumber)
            Me.Listener.Start()
            While True
                Dim IncomingClient As TcpClient = Me.Listener.AcceptTcpClient()
                Dim IncomingIP As String = IncomingClient.Client.RemoteEndPoint.ToString
                Dim IncomingClientConnection As New ClientConnection(Me, IncomingClient)
                Me.Clients.Add(IncomingIP, IncomingClientConnection)
                'New connection found
                RaiseEvent ClientConnected(IncomingIP)
            End While
        Catch
        End Try
    End Sub
...still don't know how to fix it, but hopefully getting closer.
 
If control was created on another thread than the one your currently working in you have invoke the delegate to the other thread for thread safety. The control expose the InvokeRequired property that returns boolean about this.
Example thread safe sub method you can call instead of addressing the control directly from wrong thread:
VB.NET:
Expand Collapse Copy
Private Delegate Sub dlgUpdateUI(ByVal text As String)
 
Sub updateUI(ByVal text As String)
    If listbox_clients.InvokeRequired = True Then
        Dim d As New dlgUpdateUI(AddressOf updateUI)
        listbox_clients.Invoke(d, text)
    Else
        Me.listbox_clients.Items.Add(text)
    End If
End Sub
A common fix for events raised from custom classes that are raised cross-thread is to make the delegate call inside the class before raising the event, to do this the class needs a reference to the 'ui' thread - for example by passing the 'Me' (form) instance object to the class enables the class to check invokerequired and invoke for this and all controls it contains.
Another little bit more advanced is to check the invokation list of the event delegate and invoke if needed to each subscribed delegates target, but this also means you have to let go of the regular raiseevents statement and "invoke the event method manually".
 
Back
Top