Ошибка чтения SerialPort из-за отсутствия владельца потока - PullRequest
1 голос
/ 18 сентября 2009

У меня есть простое приложение 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)

Ответы [ 3 ]

7 голосов
/ 18 сентября 2009

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

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

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

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

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

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

txtSerialOutput.Dispatcher.Invoke

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

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

2 голосов
/ 18 сентября 2009

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

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

2 голосов
/ 18 сентября 2009

Основываясь на ответе Will , я теперь решил свою проблему. Я думал, что проблема была в доступе к 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...