Pergunta

Eu tenho um aplicativo simples janelas WPF tentando ler uma porta serial com o System.IO.Ports.SerialPort.

Quando tento ler os dados de entrada no evento DataReceived, eu recebo uma exceção dizendo que eu não tenho acesso para o segmento. Como posso resolver isso?

Eu tenho isso na classe de janela 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

Quando os disparadores de eventos DataReceived, eu recebo a seguinte exceção em 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)
Foi útil?

Solução

Bem-vindo O MÁGICO WORLD OF multithreading !!!

O que está acontecendo é que todos os seus elementos de interface do usuário (instâncias de classes) só pode ser acessado / atualizado pelo segmento. Eu não vou entrar em detalhes sobre essa afinidade segmento, mas seu um assunto importante e você deve verificá-la.

O evento onde os dados são vindo da porta serial está acontecendo em um segmento diferente do que o segmento interface do usuário . O segmento interface do usuário tem uma bomba de mensagem que as mensagens alças janelas (como cliques do mouse etc). Seu porta serial não enviar mensagens do Windows. Quando os dados vem em na porta serial uma linha completamente diferente do seu segmento interface do usuário é usada para processar a mensagem.

Assim, dentro de sua aplicação, o método mSerialPort_DataReceived está executando em um segmento diferente do seu segmento. Você pode usar os Threads depuração janela para verificar isso.

Quando você tenta atualizar seu UI, você está tentando modificar um controle com afinidade thread para o segmento interface do usuário a partir de um segmento diferente, que lança a exceção que você já viu.

TL; DR: Você está tentando modificar um elemento da interface do usuário fora do segmento. Use

txtSerialOutput.Dispatcher.Invoke

para executar sua atualização no segmento. Há aqui um exemplo de como fazer thisin o conteúdo da comunidade desta página.

O Dispatcher irá invocar o seu método no segmento interface do usuário (ele envia uma mensagem janelas para o UI dizendo "Hai Guize, executar este método kthx"), e seu método podem então atualizar com segurança a interface do usuário a partir do segmento.

Outras dicas

Com base na resposta por , eu já resolveu o meu problema. Eu pensei que o problema estava acessando mSerialPort.ReadExisting(), enquanto o problema realmente estava acessando o elemento GUI txtSerialOutput de dentro do evento DataReceived, que é executado em um segmento separado.

Eu adicionei o seguinte:

Private mBuffer As String = ""
Delegate Sub DelegateSetUiText()

Private Sub UpdateUiFromBuffer()
    txtSerialOutput.Text = mBuffer
End Sub

... e eu mudei o evento DataReceived a esta:

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

A interface do usuário só pode ser atualizado pela thread principal do aplicativo. O retorno de chamada assíncrona para o evento porta serial é tratado nos bastidores em um segmento separado. Como Will mencionado, você pode usar Dispatcher.Invoke para a fila uma mudança UI propriedade componente no segmento.

No entanto, desde que você está usando WPF, há uma solução mais elegante e idiomática usando ligações. Assumindo que os dados que você recebe na porta serial tem algum valor significativo para seus objetos de negócios, você pode ter a atualização evento DataReceived uma propriedade em um objeto e, em seguida, se ligam a interface do usuário para essa propriedade.

No código áspero:

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

Em seguida, em seu arquivo XAML, você pode vincular a caixa de texto para esta propriedade objeto:

<TextBox Text="{Binding Path=SerialData}"/>

Isso pressupõe que o DataContext é definida como uma instância de sua classe MySerialData. A grande coisa sobre como fazer isso um pouco mais de encanamento é que WPF agora vai lidar com todo o empacotamento cross-thread para você automagicamente, assim você não precisa se preocupar com qual thread está invocando mudanças de interface do usuário, o mecanismo de ligação em WPF só torna trabalhos. Obviamente, se este é apenas um pulo do projeto pode não valer a pena o pouco extra de código inicial. No entanto, se você está fazendo um monte de comunicação assíncrona e atualizar o UI esta característica do WPF é uma poupança de vida real e elimina uma grande classe de erros comuns a aplicações multi-threaded. Usamos WPF em um aplicativo fortemente rosca fazendo um monte de comunicação TCP e as ligações em WPF foram ótimos, especialmente quando os dados que vêm sobre o fio destina-se a actualizar vários lugares na interface do usuário desde que você não tem que ter enfiar verificação espalhados por todo seu código.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top