Serialport Lesen Ursache Fehler wegen nicht Thread zu besitzen
-
22-07-2019 - |
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)
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.