none
UDPClient.BeginReceive and threading issue RRS feed

  • Question

  • OK, I have a simple udp listener class that I would like to get data to the UI. However, when I call the callback function it is on a different thread than the UI...so my question is how to I get the data that I receive back on the UI? I am trying to do it with events (ideal for asynchronous operations) but even the event calls are on the different thread.

    Any help is very appreciated!

    Neech

    Imports System.Net
    Imports System.Net.Sockets
    Imports System.Text
    
    Public Class UDPListener
        Public Property port As Integer
        Private interrupt As Boolean = False
        Public Property state As UdpState
        Public Property client As UdpClient
    
        Public Sub New(ByVal port As Integer)
            Me.port = port
            Dim endpoint As New IPEndPoint(IPAddress.Any, Me.port)
            client = New UdpClient(endpoint)
    
            state = New UdpState()
            state.e = endpoint
            state.u = client
        End Sub
    
        ' when this fires it appears to be on the new thread started by the
        ' beginreceive method...I thought callbacks were supposed to be back
        ' on the UI thread...
        Public Sub ReceiveCallback(ByVal ar As IAsyncResult)
    
            Dim u As UdpClient = CType((CType(ar.AsyncState, UdpState)).u, UdpClient)
            Dim e As IPEndPoint = CType((CType(ar.AsyncState, UdpState)).e, IPEndPoint)
    
            Dim receiveBytes() As Byte = u.EndReceive(ar, e)
            Dim receiveString As String = Encoding.ASCII.GetString(receiveBytes)
    
            ' when I raise this event I get a cross thread error when displaying
            ' the message
            RaiseEvent OnMessageReceived(Me, receiveString)
    
            If Me.interrupt = False Then
                client.BeginReceive(New AsyncCallback(AddressOf ReceiveCallback), state)
            Else
                RaiseEvent OnStopped(Me, "Stopped listening.")
            End If
    
        End Sub
    
        Public Sub StartListening()
            ' starts a new thread
            client.BeginReceive(New AsyncCallback(AddressOf ReceiveCallback), state)
            RaiseEvent OnStarted(Me, "Listening on port " & port)
        End Sub
    
        Public Sub StopListening()
            Me.interrupt = True
        End Sub
    
        Public Event OnStarted(ByVal sender As Object, ByVal message As String)
        Public Event OnMessageReceived(ByVal sender As Object, ByVal message As String)
        Public Event OnError(ByVal sender As Object, ByVal message As String)
        Public Event OnStopped(ByVal sender As Object, ByVal message As String)
    
    End Class
    


    neech

    Tuesday, June 14, 2016 8:07 PM

Answers

  • Hi neech,

    Thank you for your post. Based on my experience, all the update UI code should placed in Form class. If your UDPListener class is used by multi forms. I suggest you create a interface to define the specification and all the forms which use UDPListener class should implement this interface. For example, I create a interface named IUpdateUI and it contains a method UpdateUI. Of course, it can contain multiple methods.

    Public Interface IUpdateUI
        Sub UpdateUI()
    End Interface

    If a form uses UDPListener class, it need to implement IUpdateUI interface and override all its method.

    Public Class Form1
        Implements IUpdateUI
    
        Public Sub UpdateUI() Implements IUpdateUI.UpdateUI
            'update ui in this method
        End Sub
    End Class

    In UDPListener class, we don't need to pass a form as parameter. Instead of, we need to pass a instance which implement IUpdateUI interface and invoke the methods of IUpdateUI if needed. 

    Public Sub New(ByVal updateUIInstance As IUpdateUI, ByVal port As Integer)
        Me.uiInstance = MainFormInstance
    End Sub
    Best Regards,
    Li Wang

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Wednesday, June 15, 2016 5:44 AM
    Moderator
  • Hi neech,

    >>"Actually the code above gives me a cross thread error when I receive my UDP messages..."

    Although the UpdateUI method is implement in Form class, it is run on a non-UI thread. We also need to invoke the code which update the UI elements.

    Me.Invoke(Function()
                  outputToWindow(Message)
              End Function)
    Best Regards,
    Li Wang

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Tuesday, June 28, 2016 6:50 AM
    Moderator

All replies

  • Update, this is not as clean as I would have liked it to be...but here is something that works. What I really don't like about it is passing the instance of the main form to the class. This will keep the code on the mainform a bit cleaner though...so some give and take.

    Imports System.Net
    Imports System.Net.Sockets
    Imports System.Text
    
    Public Class UDPListener
        Public Property port As Integer
        Private interrupt As Boolean = False
        Public Property state As UdpState
        Public Property client As UdpClient
        Delegate Sub FireEvent(ByVal Evnt As UDPListerEvent, ByVal theMessage As String)
        Public myDelegate As FireEvent
        Private parent As Form1
    
        Public Sub New(ByVal MainFormInstance As Form1, ByVal port As Integer)
            Me.parent = MainFormInstance
            myDelegate = New FireEvent(AddressOf FireEventMethod)
    
            Me.port = port
            Dim endpoint As New IPEndPoint(IPAddress.Any, Me.port)
            client = New UdpClient(endpoint)
    
            state = New UdpState()
            state.e = endpoint
            state.u = client
        End Sub
    
        Public Sub EventInvokeMethod(ByVal evt As UDPListerEvent, ByVal theMessage As String)
            parent.Invoke(Me.myDelegate, New Object() {evt, theMessage})
        End Sub
    
        Public Sub FireEventMethod(ByVal evnt As UDPListerEvent, ByVal theMessage As String)
            Select Case evnt
                Case UDPListerEvent.started
                    RaiseEvent OnStarted(Me, theMessage)
                Case UDPListerEvent.stopped
                    RaiseEvent OnStopped(Me, theMessage)
                Case UDPListerEvent.message
                    RaiseEvent OnMessageReceived(Me, theMessage)
            End Select
        End Sub
    
        ' when this fires it appears to be on the new thread started by the
        ' beginreceive method...I thought callbacks were supposed to be back
        ' on the UI thread...
        Public Sub ReceiveCallback(ByVal ar As IAsyncResult)
    
            Dim u As UdpClient = CType((CType(ar.AsyncState, UdpState)).u, UdpClient)
            Dim e As IPEndPoint = CType((CType(ar.AsyncState, UdpState)).e, IPEndPoint)
    
            Dim receiveBytes() As Byte = u.EndReceive(ar, e)
            Dim receiveString As String = Encoding.ASCII.GetString(receiveBytes)
    
            ' when I raise this event I get a cross thread error when displaying
            ' the message
            EventInvokeMethod(UDPListerEvent.message, receiveString)
    
            If Me.interrupt = False Then
                client.BeginReceive(New AsyncCallback(AddressOf ReceiveCallback), state)
            Else
                EventInvokeMethod(UDPListerEvent.stopped, "Stopped Listening.")
            End If
    
        End Sub
    
        Public Sub StartListening()
            ' starts a new thread
            client.BeginReceive(New AsyncCallback(AddressOf ReceiveCallback), state)
            EventInvokeMethod(UDPListerEvent.started, "Listening on port " & port)
        End Sub
    
        Public Sub StopListening()
            Me.interrupt = True
        End Sub
    
        Public Event OnStarted(ByVal sender As Object, ByVal message As String)
        Public Event OnMessageReceived(ByVal sender As Object, ByVal message As String)
        Public Event OnError(ByVal sender As Object, ByVal message As String)
        Public Event OnStopped(ByVal sender As Object, ByVal message As String)
    
        Enum UDPListerEvent
            started = 0
            stopped = 1
            message = 2
        End Enum
    End Class
    


    neech

    Tuesday, June 14, 2016 9:21 PM
  • Hi neech,

    Thank you for your post. Based on my experience, all the update UI code should placed in Form class. If your UDPListener class is used by multi forms. I suggest you create a interface to define the specification and all the forms which use UDPListener class should implement this interface. For example, I create a interface named IUpdateUI and it contains a method UpdateUI. Of course, it can contain multiple methods.

    Public Interface IUpdateUI
        Sub UpdateUI()
    End Interface

    If a form uses UDPListener class, it need to implement IUpdateUI interface and override all its method.

    Public Class Form1
        Implements IUpdateUI
    
        Public Sub UpdateUI() Implements IUpdateUI.UpdateUI
            'update ui in this method
        End Sub
    End Class

    In UDPListener class, we don't need to pass a form as parameter. Instead of, we need to pass a instance which implement IUpdateUI interface and invoke the methods of IUpdateUI if needed. 

    Public Sub New(ByVal updateUIInstance As IUpdateUI, ByVal port As Integer)
        Me.uiInstance = MainFormInstance
    End Sub
    Best Regards,
    Li Wang

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Wednesday, June 15, 2016 5:44 AM
    Moderator
  • Li Wang, I appreciate the help and what you described does work. Here's my code snippets:

    Public Interface IUpdateUI
        Sub UpdateUI(ByVal eventType As Integer, ByVal message As String)
    End Interface
    Public Class MainForm
        Implements IUpdateUI
    
        Friend WithEvents udpListener As UDPListener
    
        Public Sub UpdateUI(eventType As Integer, message As String) Implements IUpdateUI.UpdateUI
    
            Select Case eventType
                Case 0, 1
                    outputToWindow(message)
                Case 2
                    ' receives the message
    
            End Select
    
            'Throw New NotImplementedException()
        End Sub

    In the UDP Listener class

        Public Sub StartListening()
            ' starts a new thread
            client.BeginReceive(New AsyncCallback(AddressOf ReceiveCallback), state)
            'RaiseEvent OnStarted(Me, "Listening on port " & My.Settings.ListenerUDPPort & "...")
            Me.uiInstance.UpdateUI(0, "Started listening on port " & My.Settings.ListenerUDPPort & "...")
        End Sub

    However, could the interface be expanded to fire events to the mainform? At the end of the day I want nice clean events coming from the UDP listener class. Similar to like the built-in Timer class or backgroundworker class. Both classes utilize multiple threads (I think) and they both fire events right to the main form.

    Again, thank you for the help!!!

    neech


    neech

    Wednesday, June 15, 2016 1:41 PM
  • Here's my desired code in the main form for the UDP class as I described above:

    How do I get it to work this way?

    Private Sub listener_OnStarted(ByVal sender As Object, ByVal message As String) Handles udpListener.OnStarted
            Me.outputToWindow(message)
            Me.StartButton.Enabled = False
            Me.StopButton.Enabled = True
        End Sub
    
        Private Sub listener_OnStopped(ByVal sender As Object, ByVal message As String) Handles udpListener.OnStopped
            Me.outputToWindow(message)
            Me.StartButton.Enabled = True
            Me.StopButton.Enabled = False
        End Sub
    
    
        Private Sub listener_OnReceivedMessage(ByVal sender As Object, ByVal message As String) Handles udpListener.OnMessageReceived
            Me.outputToWindow("Message received: " & message)
            Dim newMsg As New ScaleMessage.Message(message)
            LogData(newMsg)
        End Sub


    neech

    Wednesday, June 15, 2016 1:44 PM
  • Li Wang,

    Actually the code above gives me a cross thread error when I receive my UDP messages...

    Public Sub UpdateUI(eventType As Integer, message As String) Implements IUpdateUI.UpdateUI
    
            Select Case eventType
                Case 0, 1
                    outputToWindow(message)
                Case 2
                    ' receives the message
                    outputToWindow(message)
            End Select
    
            'Throw New NotImplementedException()
        End Sub


    neech

    Wednesday, June 15, 2016 2:07 PM
  • Hi neech,

    >>"Actually the code above gives me a cross thread error when I receive my UDP messages..."

    Although the UpdateUI method is implement in Form class, it is run on a non-UI thread. We also need to invoke the code which update the UI elements.

    Me.Invoke(Function()
                  outputToWindow(Message)
              End Function)
    Best Regards,
    Li Wang

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Tuesday, June 28, 2016 6:50 AM
    Moderator