Ошибка чтения SerialPort из-за отсутствия владельца потока

StackOverflow https://stackoverflow.com/questions/1443944

  •  22-07-2019
  •  | 
  •  

Вопрос

У меня есть простое приложение Windows WPF, которое пытается прочитать последовательный порт с помощью 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)
Это было полезно?

Решение

ДОБРО ПОЖАЛОВАТЬ В ВОЛШЕБНЫЙ МИР МУЛЬТИТРЕЙДА !!!

Что происходит, так это то, что все ваши элементы пользовательского интерфейса (экземпляры классов) могут быть доступны / обновлены только потоком пользовательского интерфейса. Я не буду вдаваться в подробности об этой близости темы, но это важная тема, и вы должны это проверить.

Событие, когда данные поступают через последовательный порт, происходит в потоке , отличном от потока пользовательского интерфейса . В потоке пользовательского интерфейса есть насос сообщений, который обрабатывает сообщения Windows (например, щелчки мыши и т. Д.). Ваш последовательный порт не отправляет сообщения Windows. Когда данные поступают на последовательный порт, для обработки этого сообщения используется совершенно другой поток из потока пользовательского интерфейса.

Итак, в вашем приложении метод mSerialPort_DataReceived выполняется в потоке, отличном от потока вашего пользовательского интерфейса. Вы можете использовать окно отладки Threads, чтобы проверить это.

Когда вы пытаетесь обновить свой пользовательский интерфейс, вы пытаетесь изменить элемент управления с привязкой потока для потока пользовательского интерфейса из другого потока, который выдает исключение, которое вы видели.

TL; DR: вы пытаетесь изменить элемент пользовательского интерфейса вне потока пользовательского интерфейса. Используйте

txtSerialOutput.Dispatcher.Invoke

чтобы запустить обновление в потоке пользовательского интерфейса. Здесь приведен пример того, как это сделать, в контенте этого сообщества. стр.

Диспетчер вызовет ваш метод в потоке пользовательского интерфейса (он отправляет оконное сообщение для пользовательского интерфейса, говорящее «Привет, гайзер, запустите этот метод kthx»), и ваш метод сможет затем безопасно обновить пользовательский интерфейс из потока пользовательского интерфейса.

Другие советы

Пользовательский интерфейс может обновляться только основным потоком приложения. Асинхронный обратный вызов для события последовательного порта обрабатывается за кулисами в отдельном потоке. Как уже упоминалось, вы можете использовать Dispatcher.Invoke, чтобы поставить в очередь изменение свойства компонента пользовательского интерфейса в потоке пользовательского интерфейса.

Тем не менее, поскольку вы используете WPF, есть более элегантный & amp; идиоматическое решение с использованием привязок. Предполагая, что данные, которые вы получаете через последовательный порт, имеют какое-то существенное значение для ваших бизнес-объектов, вы можете заставить событие DataReceived обновить свойство в объекте и затем связать пользовательский интерфейс с этим свойством.

В грубом коде:

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 теперь будет автоматически обрабатывать весь процесс межпотокового маршалинга, поэтому вам не нужно беспокоиться о том, какой поток вызывает изменения пользовательского интерфейса, механизм связывания в WPF просто делает это Работа. Очевидно, что если это простой проект, он может не стоить лишнего кода. Однако, если вы выполняете много асинхронного взаимодействия и обновляете пользовательский интерфейс, эта функция WPF действительно спасает жизнь и устраняет большой класс ошибок, общих для многопоточных приложений. Мы используем WPF в многопоточных приложениях, выполняющих много TCP-коммуникаций, и привязки в WPF были великолепны, особенно когда данные, передаваемые по проводам, предназначены для обновления нескольких мест в пользовательском интерфейсе, так как вам не нужно разбрасывать проверку потоков ваш код.

scroll top