views:

963

answers:

3

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)
+5  A: 

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.

Will
Do you know a similar access to a `Dispatcher` object for a `Windows.Forms` scenario (not WPF)?
awe
All windows forms controls have a property called InvokeRequired and a method called Invoke. They do the same thing the Dispatcher does in WPF. Just google both terms and you'll find lots of docs on the subject.
Will
A: 

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
awe
+1  A: 

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.

gbc
The idea of this sounds promising. I tried it but it didn't work. It compiled an ran without errors, but the content of the textbox was not updated. I went back to the other solution which works... I gave you a +1 for the principle of it. It will probably work in other scenarios...
awe
Yeah, it can be a little tricky to get right the first time since bindings just fail silently when there's a problem. When you run, check the console output in visual studio because binding errors will be logged out to the VS console. You can set the DataContext in either your code-behind class, or in your resources section of the XAML. Thanks for the up-vote, hope you can get it working because it is really slick once you get it worked out the first time.
gbc