Question

J'ai une simple application Windows WPF qui tente de lire un port série avec le System.IO.Ports.SerialPort .

Lorsque j'essaie de lire les données entrantes dans l'événement DataReceived , j'obtiens une exception indiquant que je n'ai pas accès au fil. Comment le résoudre?

J'ai ceci dans la classe de fenêtre 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

Lorsque l'événement DataReceived se déclenche, l'exception suivante apparaît sur 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)
Était-ce utile?

La solution

BIENVENUE DANS LE MONDE MAGIQUE DU MULTI THREADING !!!

Ce qui se passe, c'est que tous les éléments de votre interface utilisateur (instances de classe) ne peuvent être accessibles / mis à jour que par le thread d'interface utilisateur. Je n’entrerai pas dans les détails de cette affinité avec les fils, mais c’est un sujet important et vous devriez le vérifier.

L'événement où des données arrivent sur le port série se produit sur un thread différent de celui de l'interface utilisateur . Le thread d'interface utilisateur a une pompe de messages qui gère les messages Windows (comme les clics de souris, etc.). Votre port série n'envoie pas de messages Windows. Lorsque des données arrivent sur le port série, un thread complètement différent de celui de votre interface utilisateur est utilisé pour traiter ce message.

Ainsi, dans votre application, la méthode mSerialPort_DataReceived s'exécute dans un thread différent de celui de votre interface utilisateur. Vous pouvez utiliser la fenêtre de débogage des threads pour vérifier cela.

Lorsque vous essayez de mettre à jour votre interface utilisateur, vous essayez de modifier un contrôle avec une affinité de thread pour le thread d'interface utilisateur à partir d'un thread différent, qui lève l'exception que vous avez vue.

TL; DR: Vous essayez de modifier un élément de l'interface utilisateur en dehors du fil de l'interface utilisateur. Utilisez

txtSerialOutput.Dispatcher.Invoke

pour exécuter votre mise à jour sur le fil de l'interface utilisateur. Il y a un exemple ici pour savoir comment faire cela dans le contenu de la communauté de ceci page.

Dispatcher invoquera votre méthode sur le thread d'interface utilisateur (il enverra un message Windows à l'interface utilisateur en indiquant "Hai guize, exécutez cette méthode kthx"), et votre méthode pourra alors mettre à jour en toute sécurité l'interface utilisateur à partir du thread d'interface utilisateur.

Autres conseils

D'après la réponse fournie par Fera , j'ai maintenant résolu mon problème. Je pensais que le problème était d'accéder à mSerialPort.ReadExisting () , alors que le problème était d'accéder à l'élément graphique txtSerialOutput à partir de l'événement DataReceived , qui s'exécute dans un thread séparé.

J'ai ajouté ceci:

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

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

... et j'ai remplacé l'événement DataReceived par ceci:

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

L’UI ne peut être mise à jour que par le fil principal de l’application. Le rappel asynchrone pour l'événement de port série est traité en arrière-plan sur un thread distinct. Comme Will l'a mentionné, vous pouvez utiliser Dispatcher.Invoke pour mettre en file d'attente un changement de propriété de composant d'interface utilisateur sur le thread d'interface utilisateur.

Cependant, puisque vous utilisez WPF, il existe une solution plus élégante & amp; solution idiomatique utilisant des liaisons. En supposant que les données que vous recevez sur le port série ont une valeur significative pour vos objets métier, vous pouvez faire en sorte que l'événement DataReceived mette à jour une propriété dans un objet, puis lie l'interface utilisateur à cette propriété.

En code brut:

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

Ensuite, dans votre fichier XAML, vous pouvez associer la zone de texte à la propriété d'objet suivante:

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

Cela suppose que le DataContext est défini sur une instance de votre classe MySerialData. Ce qui est génial avec ce travail de plomberie supplémentaire, c’est que WPF gérera automatiquement tout le marshaling inter-thread pour vous, de sorte que vous n’aurez plus à vous soucier du thread qui appelle les modifications de l’UI. Le moteur de liaison de WPF le fait simplement. travail. De toute évidence, s'il ne s'agit que d'un projet à la volée, cela ne vaut peut-être pas le code supplémentaire. Cependant, si vous faites beaucoup de communication asynchrone et mettez à jour l'interface utilisateur, cette fonctionnalité de WPF vous permettra d'économiser de la vie et d'éliminer une grande classe de bogues communs aux applications multithreads. Nous utilisons WPF dans une application fortement threadée effectuant beaucoup de communication TCP et les liaisons dans WPF étaient excellentes, en particulier lorsque les données qui arrivent sur le réseau sont destinées à mettre à jour plusieurs emplacements de l'interface utilisateur, car il n'est pas nécessaire que la vérification du threading soit omniprésente. votre code.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top