簡體   English   中英

由於不擁有線程,SerialPort讀取導致錯誤

[英]SerialPort reading cause error because of not owning thread

我有一個簡單的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)

歡迎來到多重閱讀的神奇世界!!!

發生的事情是您的所有UI元素(類實例)只能由UI線程訪問/更新。 我不會詳細介紹該線程關聯性,但是它是一個重要的主題,您應該檢查一下。

串行端口上輸入數據的事件發生在與UI線程不同的線程上 UI線程具有處理Windows消息(如鼠標單擊等)的消息泵。 您的串行端口不會發送Windows消息。 當數據進入串行端口時,將使用與UI線程完全不同的線程來處理該消息。

因此,在您的應用程序中,mSerialPort_DataReceived方法在與UI線程不同的線程中執行。 您可以使用“線程”調試窗口進行驗證。

當您嘗試更新UI時,您試圖修改具有與來自其他線程的UI線程的線程相似性的控件,這將引發您所看到的異常。

TL; DR:您正在嘗試在UI線程之外修改UI元素。 采用

txtSerialOutput.Dispatcher.Invoke

在UI線程上運行更新。 在此頁面的社區內容中,有一個有關如何執行此操作的示例。

Dispatcher將在UI線程上調用您的方法(它向Windows發送一條Windows消息,提示“ Hai guize,運行此方法kthx”),然后您的方法可以從UI線程安全地更新UI。

根據Will的回答,我現在解決了我的問題。 我認為問題出在訪問mSerialPort.ReadExisting() ,而問題實際上出在從DataReceived事件內部訪問GUI元素txtSerialOutput ,該事件在單獨的線程中運行。

我添加了這個:

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

UI只能由主應用程序線程更新。 串行端口事件的異步回調在單獨的線程中在后台處理。 如Will所述,您可以使用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中的多個位置時,因為您不必在整個過程中進行線程檢查您的代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM