Results 1 to 9 of 9

Thread: Avoiding Cross Thread warnings during SerialPort.ReceivedData events

  1. #1

    Thread Starter
    Junior Member
    Join Date
    Apr 2020
    Posts
    24

    Avoiding Cross Thread warnings during SerialPort.ReceivedData events

    My program reads a serialport data packet stream from a remote device. The serialport read is event driven:

    Code:
    Private Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
    The data packet received is fixed length string where each character ascii value represents a range of device state conditions, e.g. Power level ranging from ASC(#) to ASC(z) and then my code processes and displays the interpreted values onto a form as a first step in a polynomial regression analysis of the data. The remote device sends new data packets only when some device internal state has changed or is changing in real-time. So it may send out one packet as a device 'state' snapshot and then nothing for few seconds, or at other times a stream of dozen packets within a fraction of a second can appear. With each of these events, I need some method to wait a minimum time, e.g. 0.3 seconds, after the first packet is received to see if others will follow immediately and if not then to just use the 1 or few packets received and average them.

    I have been using the following function (on Module level) for fractions of a second pause but sometimes it misses and clips a cluster of packets too soon. I suspect this is because the Wait function is not on the same thread as the serialport. Can someone advise the best coding method for my circumstance??

    Code:
     Public Sub Wait(ByVal Seconds As Double, Optional ByRef BreakCondition As Boolean = False)
            Dim l_WaitUntil As Date
            l_WaitUntil = Now.AddSeconds(Seconds)
            Do Until Now > l_WaitUntil
                If BreakCondition Then Exit Do
                System.Windows.Forms.Application.DoEvents()
            Loop
        End Sub

  2. #2

    Thread Starter
    Junior Member
    Join Date
    Apr 2020
    Posts
    24

    Re: Avoiding Cross Thread warnings during SerialPort.ReceivedData events

    Some additional information. My serialport read event is:

    Code:
    If SerialPort.BytesToRead > 0 Then
                     ReceiveBuffer = SerialPort.ReadLine
                    'ReceiveBuffer = SerialPort.ReadExisting
    End If
    My data packets do not have a reliable prefix or suffix (LF, CR, space, etc) that separates the packets. The packets are 7 characters in length and I found that .Readexisting led to strings being broken. ReadLine apeears the most reliable because it seems the device separate sending consecutive packets with a fraction of time. I was thinking of using an oscilloscope to measure the serial stream when a cluster is being sent and measure the time interval the device uses to separate packets at 9600 baud but I would still face the programming challenge of introducing a timed pause.

  3. #3

    Thread Starter
    Junior Member
    Join Date
    Apr 2020
    Posts
    24

    Re: Avoiding Cross Thread warnings during SerialPort.ReceivedData events

    I misspoke there is a hex CR-LF after each packet 0x0D 0x0A that is why .Readline works better than .ReadExisting but that doesn't impact my problem of not knowing if another data packet is coming soon after the preceding.

  4. #4
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,297

    Re: Avoiding Cross Thread warnings during SerialPort.ReceivedData events

    The DataReceived event is raised on a secondary thread so that means that you cannot access UI elements directly. You need to marshal a method call to the UI thread in order to access your controls on that thread. To learn how to do that, follow the CodeBank link in my signature below and check out my thread on Accessing Controls From Worker Threads.

  5. #5
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Avoiding Cross Thread warnings during SerialPort.ReceivedData events

    I think I would just use a Concurrent Queue of String.
    The DataReceived event would do the readline, and enqueue the string received on the queue (the concurrent queue is designed to be accessible from multiple threads).

    Decide how often you want to check for data, say twice a second so set the interval to 500, or perhaps four times a second, so 250, and then just check the queue to see if some strings have been received. If you see that there is some number of strings in the queue in one pass, and when you comeback and have the same number in the queue the second pass, then you've reached a gap in the data stream, so read that number of strings from the queue and do your averaging.

    Another option if you're not regularly updating the display at a fixed rate, so don't have a periodic timer event that can check the queue, you could still have the DataReceived event queuing the strings, but it would also enable a timer (by invoking since I assume you would use a winform timer) so you would get a timer event some number of milliseconds after the first reception (it would enable the timer for every reception, but the secondary enablings shouldn't affect the time interval, I wouldn't think (haven't tested the options).
    When the timer fires, you disable it, read the items from the queue and process them.
    When the next data are received, the timer will be enabled again, and you'll get the tick event after the delay you've chosen and you process the next set.
    "Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930

  6. #6

    Thread Starter
    Junior Member
    Join Date
    Apr 2020
    Posts
    24

    Re: Avoiding Cross Thread warnings during SerialPort.ReceivedData events

    Quote Originally Posted by jmcilhinney View Post
    The DataReceived event is raised on a secondary thread so that means that you cannot access UI elements directly. You need to marshal a method call to the UI thread in order to access your controls on that thread. To learn how to do that, follow the CodeBank link in my signature below and check out my thread on Accessing Controls From Worker Threads.
    Thank you, I do use that method:
    Code:
    Private Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
            If SerialPort.BytesToRead > 0 Then
                    ReceiveBuffer = SerialPort.ReadLine
                    'ReceiveBuffer = SerialPort.ReadExisting
                End If
            End If
    
            DefInstance.txtTerm.BeginInvoke(New DisplayData(AddressOf Display), ReceiveBuffer)
            ReceiveBuffer = ""
        End Sub
    
        Public Delegate Sub DisplayData(ByVal Buffer As String)   
        
        Private Sub Display(ByVal Buffer As String)
            With DefInstance.txtTerm
                .SelectedText = Buffer
                RM_GPIO_Update(Buffer)                               
           End With
     End Sub
    I start all the business-end processing with the RM_GPIO_Update(Buffer) routine which processes the received string via other subs and functions. These other functions/routines occasionally will send a Serialport.Write("command") to the external device. Does that create another thread separate from the Serialport.ReadLine or from the UI controls? I only have one instance of Serialport declared.

  7. #7

    Thread Starter
    Junior Member
    Join Date
    Apr 2020
    Posts
    24

    Re: Avoiding Cross Thread warnings during SerialPort.ReceivedData events

    I apologize for not showing all the code. I should have added that I make the serialport declarations and instantiate it in a form "AlphaSerialTerm". Should I instead place this all into a separate module rather than then main form?

    Code:
    Public Class AlphaSerialTerm
        'for serialport handling
        Public WithEvents SerialPort As SerialPort
        Private m_FormDefInstance As AlphaSerialTerm

  8. #8
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,297

    Re: Avoiding Cross Thread warnings during SerialPort.ReceivedData events

    That code is weird. What is that m_FormDefInstance field for? Why does that form need a reference to another form of the same type? How is that field populated? How is the DefInstance variable populated? If that code is inside the form that you want to update then why are you not just referring to the form via Me? If the code in post #6 is in the form that contains the TextBox you want to update then you should be referring to the current instance:
    Code:
        Private Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort.DataReceived
            If SerialPort.BytesToRead > 0 Then
                    ReceiveBuffer = SerialPort.ReadLine
                    'ReceiveBuffer = SerialPort.ReadExisting
                End If
            End If
    
            Me.txtTerm.BeginInvoke(New DisplayData(AddressOf Display), ReceiveBuffer)
            ReceiveBuffer = ""
        End Sub
    
        Public Delegate Sub DisplayData(ByVal Buffer As String)   
        
        Private Sub Display(ByVal Buffer As String)
            With Me.txtTerm
                .SelectedText = Buffer
                RM_GPIO_Update(Buffer)                               
           End With
        End Sub
    You can drop the explicit Me. if you want and it will be used implictly.

    Also, there's little point declaring your own delegate type these days. Just use an Action for Subs and a Func for Functions. Both are generic and can handle up to 16 parameters. In your case, an Action(Of String) would be the appropriate choice.

  9. #9

    Thread Starter
    Junior Member
    Join Date
    Apr 2020
    Posts
    24

    Re: Avoiding Cross Thread warnings during SerialPort.ReceivedData events

    Quote Originally Posted by jmcilhinney View Post
    That code is weird. What is that m_FormDefInstance field for? Why does that form need a reference to another form of the same type? How is that field populated? How is the DefInstance variable populated? If that code is inside the form that you want to update then why are you not just referring to the form via Me? If the code in post #6 is in the form that contains the TextBox you want to update then you should be referring to the current instance: Also, there's little point declaring your own delegate type these days. Just use an Action for Subs and a Func for Functions. Both are generic and can handle up to 16 parameters. In your case, an Action(Of String) would be the appropriate choice.
    Thank you VERY much for the advise. That code snippet came in from my very early days of getting familiar with serialport handling from within a single form app. I plagiarized it from an example on a popular website :-) Should all of the serialport code be in a stand alone Class or module rather than one Form class among many forms?

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width