-
Aug 8th, 2020, 11:00 AM
#1
Thread Starter
Junior Member
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
-
Aug 8th, 2020, 11:15 AM
#2
Thread Starter
Junior Member
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.
-
Aug 8th, 2020, 12:03 PM
#3
Thread Starter
Junior Member
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.
-
Aug 8th, 2020, 01:02 PM
#4
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.
-
Aug 8th, 2020, 03:05 PM
#5
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
-
Aug 8th, 2020, 03:33 PM
#6
Thread Starter
Junior Member
Re: Avoiding Cross Thread warnings during SerialPort.ReceivedData events
Originally Posted by jmcilhinney
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.
-
Aug 8th, 2020, 04:30 PM
#7
Thread Starter
Junior Member
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
-
Aug 8th, 2020, 11:24 PM
#8
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.
-
Aug 9th, 2020, 10:10 AM
#9
Thread Starter
Junior Member
Re: Avoiding Cross Thread warnings during SerialPort.ReceivedData events
Originally Posted by jmcilhinney
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|