我有一个简单的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)
答案 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中的多个位置时,因为您不必在整个过程中进行线程检查你的代码。