문제

간단한 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 스레드에서 업데이트를 실행합니다. 이 페이지의 커뮤니티 내용을 수행하는 방법에 대한 예가 있습니다.

디스패처는 UI 스레드에서 메소드를 호출합니다 ( "Hai Guize,이 메소드 kthx를 실행"하는 UI에 Windows 메시지를 보냅니다). 그런 다음 메소드가 UI 스레드에서 UI를 안전하게 업데이트 할 수 있습니다.

다른 팁

답변을 바탕으로 할 것이다, 나는 이제 내 문제를 해결했다. 나는 문제가 접근하고 있다고 생각했다 mSerialPort.ReadExisting(), 문제가 실제로 GUI 요소에 액세스하는 동안 txtSerialOutput 내에서 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

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 변경을 호출하는지 걱정할 필요가 없다는 것입니다. 일하다. 분명히, 이것이 단지 던져 버리는 프로젝트라면 추가적인 선불 코드의 가치가 없을 수도 있습니다. 그러나 많은 비동기 통신을 수행하고 UI를 업데이트하는 경우 WPF 의이 기능은 실제 절약 장치이며 멀티 스레드 애플리케이션에 공통적 인 많은 클래스의 버그를 제거합니다. 우리는 많은 TCP 통신을 수행하는 많은 스레드 애플리케이션에서 WPF를 사용하고 WPF의 바인딩은 특히 스레딩 검사를 뿌릴 필요가 없기 때문에 와이어 위에 오는 데이터가 UI의 여러 장소를 업데이트 할 때 특히 WPF의 바인딩이 훌륭했습니다. 코드.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top