Frage

Ich habe eine einfache WPF Windows-Anwendung über eine serielle Schnittstelle mit dem System.IO.Ports.SerialPort zu lesen versuchen.

Wenn ich versuche, die eingehenden Daten in dem DataReceived Ereignisse zu lesen, erhalte ich eine Ausnahme, dass ich keinen Zugriff auf das Gewinde verfügen. Wie kann ich es lösen?

Ich habe dies in der WPF-Fenster-Klasse:

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

Wenn das DataReceived Ereignis auslöst, erhalte ich die folgende Ausnahme auf 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)
War es hilfreich?

Lösung

Willkommen in der magischen Welt des Multithreading !!!

Was passiert, ist, dass alle Ihre UI-Elemente (Klasse Instanzen) kann nur durch den UI-Thread zugegriffen / aktualisiert werden. Ich werde nicht in die Details über diesen Thread Affinität gehen, aber es ist ein wichtiges Thema, und Sie sollten es überprüfen.

Das Ereignis, bei dem Daten auf der seriellen Schnittstelle kommen in geschieht auf einem anderen Thread als dem UI-Thread . Der UI-Thread hat eine Nachricht Pumpe, die Windows-Nachrichten (wie Mausklicks usw.) behandelt. Ihre serielle Schnittstelle sendet aus nicht Windows-Nachrichten. Wenn Daten ein ganz anderer Thread von Ihrem UI-Thread in an der seriellen Schnittstelle kommen, wird verwendet, um diese Nachricht zu verarbeiten.

Also, in Ihrer Anwendung, die mSerialPort_DataReceived Verfahren in einem anderen Thread als UI-Thread ausgeführt wird. Sie können die Threads Debug-Fenster verwenden, um dies zu überprüfen.

Wenn Sie versuchen, die Benutzeroberfläche zu aktualisieren, Sie versuchen, eine Steuerung mit Gewinden Affinität für den UI-Thread von einem anderen Thread zu ändern, die die Ausnahme auslöst Sie gesehen haben.

TL; DR: Sie versuchen, ein UI-Element außerhalb des UI-Thread zu ändern. Mit

txtSerialOutput.Dispatcher.Invoke

Ihr Update auf dem UI-Thread ausgeführt. Es gibt hier ein Beispiel, wie thisin die Gemeinde Inhalt, dies zu tun Seite.

Der Dispatcher Ihre Methode auf dem UI-Thread aufrufen wird (es sendet eine Windows-Nachricht an den UI sagen „Hai Guize, diese Methode kthx laufen“), und die Methode kann dann sicher die Benutzeroberfläche aus dem UI-Thread aktualisieren.

Andere Tipps

Auf der Grundlage der Antwort von Will , habe ich jetzt gelöst mein Problem. Ich dachte, das Problem mSerialPort.ReadExisting() zugriff, während das Problem wirklich das GUI-Element txtSerialOutput aus dem DataReceived Ereignisse Zugriff wurde, die in einem separaten Thread ausgeführt wird.

Ich habe diese:

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

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

... und ich änderte das DataReceived Ereignis folgt aus:

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

Die Benutzeroberfläche kann nur durch den Hauptanwendungsthread aktualisiert werden. Der Asynchron-Rückruf für das serielle Port-Ereignis wird hinter den Kulissen auf einem separaten Thread behandelt. Wie Will erwähnte, können Sie Dispatcher.Invoke verwenden, um eine UI-Komponente Eigenschaftsänderung auf dem UI-Thread in der Warteschlange.

Da Sie jedoch WPF verwenden, gibt es eine elegantere und idiomatische Lösung unter Verwendung von Bindungen. Unter der Annahme, dass die Daten, die Sie an der seriellen Schnittstelle empfangen haben einigen bedeutenden Mehrwert für Ihre Business-Objekte, können Sie das DataReceived Ereignis eine Eigenschaft in einem Objekt aktualisieren und dann bindet die Benutzeroberfläche dieser Eigenschaft.

In grobem 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

Dann in Ihrer XAML-Datei können Sie das Textfeld an dieser Objekt-Eigenschaft binden:

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

Dies setzt voraus, dass die Datacontext auf eine Instanz Ihrer MySerialData Klasse gesetzt. Die große Sache über dieses Extra an Sanitär tun, ist, dass WPF werden nun alle von der Cross-Thread Serialisieren für Sie automatisch handhaben, so dass Sie müssen über die keine Sorge Thread UI Änderungen aufruft, macht das Bindungsmodul in WPF es einfach Arbeit. Natürlich, wenn dies nur ein wegzuwerfen Projekt ist, kann es nicht das Extra an Upfront-Code im Wert sein. wenn Sie eine Menge von Asynchron-Kommunikation jedoch tun, und die Benutzeroberfläche dieser Funktion von WPF Aktualisierung ist ein echter Lebensretter und beseitigt eine große Klasse von Bugs gemeinsam Multi-Threaded-Anwendungen. Wir verwenden WPF in einem stark Threaded-Anwendung viele TCP-Kommunikation zu tun und die Bindungen in WPF waren besonders groß, wenn die Daten über den Draht kommen an mehreren Stellen in der Benutzeroberfläche aktualisieren soll, da Sie müssen im gesamten nicht Threading Prüfung haben bestreut Ihr Code.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top