SerialPort reading cause error because of not owning thread
-
22-07-2019 - |
Question
I have a simple WPF windows application trying to read a serial port with the System.IO.Ports.SerialPort
.
When I try to read the incoming data in the DataReceived
event, I get an exception saying that I don't have access to the thread. How do I solve it?
I have this in the WPF window class:
Public WithEvents mSerialPort As New SerialPort()
Private Sub btnConnect_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnConnect.Click
With mSerialPort
If .IsOpen Then
.Close()
End If
.BaudRate = 4800
.PortName = SerialPort.GetPortNames()(0)
.Parity = Parity.None
.DataBits = 8
.StopBits = StopBits.One
.NewLine = vbCrLf
.Open()
End With
End Sub
Private Sub mSerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles mSerialPort.DataReceived
If e.EventType = SerialData.Chars Then
txtSerialOutput.Text += mSerialPort.ReadExisting()
End If
End Sub
Protected Overrides Sub Finalize()
If mSerialPort.IsOpen Then
mSerialPort.Close()
End If
mSerialPort.Dispose()
mSerialPort = Nothing
MyBase.Finalize()
End Sub
When the DataReceived
event triggers, I get the following exception on mSerialPort.ReadExisting()
:
System.InvalidOperationException was unhandled
Message="The calling thread cannot access this object because a different thread owns it."
Source="WindowsBase"
StackTrace:
at System.Windows.Threading.Dispatcher.VerifyAccess() at System.Windows.Threading.DispatcherObject.VerifyAccess() at System.Windows.DependencyObject.GetValue(DependencyProperty dp) at System.Windows.Controls.TextBox.get_Text() at Serial.Serial.mSerialPort_DataReceived(Object sender, SerialDataReceivedEventArgs e) in D:\SubVersion\VisionLite\Serial\Serial.xaml.vb:line 24 at System.IO.Ports.SerialPort.CatchReceivedEvents(Object src, SerialDataReceivedEventArgs e) at System.IO.Ports.SerialStream.EventLoopRunner.CallReceiveEvents(Object state) at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack) at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)
Solution
WELCOME TO THE MAGICAL WORLD OF MULTITHREADING!!!
What's happening is that all of your UI elements (class instances) can only be accessed/updated by the UI thread. I won't go into the details about this thread affinity, but its an important subject and you should check it out.
The event where data is coming in on the serial port is happening on a different thread than the UI thread. The UI thread has a message pump that handles windows messages (like mouse clicks etc). Your serial port doesn't send off windows messages. When data comes in on the serial port a completely different thread from your UI thread is used to process that message.
So, within your application, the mSerialPort_DataReceived method is executing in a different thread than your UI thread. You can use the Threads debugging window to verify this.
When you attempt to update your UI, you are trying to modify a control with thread affinity for the UI thread from a different thread, which throws the exception you've seen.
TL;DR: You are trying to modify a UI element outside of the UI thread. Use
txtSerialOutput.Dispatcher.Invoke
to run your update on the UI thread. There's an example here on how to do thisin the community content of this page.
The Dispatcher will Invoke your method on the UI thread (it sends a windows message to the UI saying "Hai guize, run this method kthx"), and your method can then safely update the UI from the UI thread.
OTHER TIPS
Based on the answer by Will, I have now solved my problem. I thought the problem was accessing mSerialPort.ReadExisting()
, while the problem really was accessing the GUI element txtSerialOutput
from within the DataReceived
event, which runs in a separate thread.
I added this:
Private mBuffer As String = ""
Delegate Sub DelegateSetUiText()
Private Sub UpdateUiFromBuffer()
txtSerialOutput.Text = mBuffer
End Sub
...and I changed the DataReceived
event to this:
Private Sub mSerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles mSerialPort.DataReceived
If e.EventType = SerialData.Chars Then
mBuffer += mSerialPort.ReadExisting()
txtSerialOutput.Dispatcher.Invoke(New DelegateSetUiText(AddressOf UpdateUiFromBuffer))
End If
End Sub
The UI may only be updated by the main application thread. The async callback for the serial port event is handled behind the scenes on a separate thread. As Will mentioned, you can use Dispatcher.Invoke to queue a UI component property change on the UI thread.
However, since you're using WPF, there's a more elegant & idiomatic solution using bindings. Assuming that the data you receive on the serial port has some significant value to your business objects, you can have the DataReceived event update a property in an object and then bind the UI to that property.
In rough code:
Public Class MySerialData
Implements System.ComponentModel.INotifyPropertyChanged
Public Event PropertyChanged(sender as Object, e as System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotfifyPropertyChanged.PropertyChanged
private _serialData as String
Public Property SerialData() As String
Get
Return _serialData
End Get
Set(value as String)
If value <> _serialData Then
_serialData = value
RaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("SerialData"))
End If
End Property
Then in your XAML file, you can bind the text box to this object property:
<TextBox Text="{Binding Path=SerialData}"/>
This assumes that the DataContext is set to an instance of your MySerialData class. The great thing about doing this extra bit of plumbing is that WPF will now handle all of the cross-thread marshaling for you automagically so you don't have to worry about which thread is invoking UI changes, the binding engine in WPF just makes it work. Obviously, if this is just a throw away project it may not be worth the extra bit of upfront code. However, if you are doing a lot of async communication and updating the UI this feature of WPF is a real life saver and eliminates a large class of bugs common to multi-threaded applications. We use WPF in a heavily threaded application doing a lot of TCP communication and the bindings in WPF were great especially when the data coming over the wire is meant to update several places in the UI since you don't have to have threading check sprinkled throughout your code.