由于不拥有线程,SerialPort读取会导致错误

时间:2009-09-18 11:26:40

标签: .net wpf vb.net serial-port

我有一个简单的WPF Windows应用程序试图读取System.IO.Ports.SerialPort的串行端口。

当我尝试读取DataReceived事件中的传入数据时,我得到一个例外,说我无法访问该线程。我该如何解决?

我在WPF窗口类中有这个:

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

DataReceived事件触发时,我在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)

3 个答案:

答案 0 :(得分:7)

欢迎来到多元化的神奇世界!!!

发生的事情是,所有UI元素(类实例)只能由UI线程访问/更新。我不会详细介绍这个线程关联,但它是一个重要的主题,你应该检查一下。

串行端口上的数据进入的事件发生在与UI线程不同的线程上。 UI线程有一个消息泵来处理Windows消息(如鼠标点击等)。您的串行端口不会发送Windows消息。当数据进入串行端口时,将使用与UI线程完全不同的线程来处理该消息。

因此,在您的应用程序中,mSerialPort_DataReceived方法在与您的UI线程不同的线程中执行。您可以使用Threads调试窗口来验证这一点。

当您尝试更新UI时,您尝试从另一个线程修改具有线程关联性的UI线程的控件,这会抛出您看到的异常。

TL; DR:您正在尝试修改UI线程之外的UI元素。使用

txtSerialOutput.Dispatcher.Invoke

在UI线程上运行更新。 There's an example here on how to do thisin the community content of this page.

Dispatcher将在UI线程上调用您的方法(它向UI发送一条窗口消息,说“Hai guize,运行此方法kthx”),然后您的方法可以安全地从UI线程更新UI。

答案 1 :(得分:2)

根据Will的答案,我现在已经解决了我的问题。我认为问题是访问mSerialPort.ReadExisting(),而问题实际上是从txtSerialOutput事件中访问GUI元素DataReceived,该事件在一个单独的线程中运行。

我补充说:

Private mBuffer As String = ""
Delegate Sub DelegateSetUiText()

Private Sub UpdateUiFromBuffer()
    txtSerialOutput.Text = mBuffer
End Sub

...我将DataReceived事件更改为:

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

答案 2 :(得分:2)

UI只能由主应用程序线程更新。串行端口事件的异步回调在单独线程的后台处理。如前所述,您可以使用Dispatcher.Invoke在UI线程上对UI组件属性更改进行排队。

但是,由于你使用的是WPF,因此它更加优雅。使用绑定的惯用解决方案。假设您在串行端口上收到的数据对业务对象具有一些重要价值,您可以让DataReceived事件更新对象中的属性,然后将UI绑定到该属性。

粗略的代码:

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

然后在您的XAML文件中,您可以将文本框绑定到此对象属性:

<TextBox Text="{Binding Path=SerialData}"/>

这假定DataContext设置为MySerialData类的实例。做这个额外的管道的好处是,WPF现在将自动处理所有的跨线程编组,因此您不必担心哪个线程正在调用UI更改,WPF中的绑定引擎只是使它成为它工作。显然,如果这只是一个扔掉的项目,可能不值得额外的前期代码。但是,如果您正在进行大量异步通信并更新UI,则WPF的这一功能可以节省大量时间,并消除了多线程应用程序常见的大量错误。我们在执行大量TCP通信的高线程应用程序中使用WPF,并且WPF中的绑定非常好,特别是当通过线路传输的数据旨在更新UI中的多个位置时,因为您不必在整个过程中进行线程检查你的代码。