قراءة 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 في مؤشر ترابط مختلف عن مؤشر ترابط واجهة المستخدم الخاص بك.يمكنك استخدام نافذة تصحيح المواضيع للتحقق من ذلك.

عندما تحاول تحديث واجهة المستخدم الخاصة بك، فإنك تحاول تعديل عنصر تحكم مع تقارب مؤشر الترابط لمؤشر ترابط واجهة المستخدم من مؤشر ترابط مختلف، مما يؤدي إلى الاستثناء الذي رأيته.

ليرة تركية؛دكتور:أنت تحاول تعديل عنصر واجهة المستخدم خارج مؤشر ترابط واجهة المستخدم.يستخدم

txtSerialOutput.Dispatcher.Invoke

لتشغيل التحديث الخاص بك على مؤشر ترابط واجهة المستخدم. يوجد مثال هنا حول كيفية القيام بذلك في محتوى مجتمع هذه الصفحة.

سوف يقوم المرسل باستدعاء طريقتك في مؤشر ترابط واجهة المستخدم (يرسل رسالة Windows إلى واجهة المستخدم تقول "Hai guize، قم بتشغيل هذه الطريقة kthx")، ويمكن لطريقتك بعد ذلك تحديث واجهة المستخدم بأمان من مؤشر ترابط واجهة المستخدم.

نصائح أخرى

بناءً على إجابة سوف, ، لقد قمت الآن بحل مشكلتي.اعتقدت أن المشكلة كانت في الوصول mSerialPort.ReadExisting(), ، بينما كانت المشكلة بالفعل هي الوصول إلى عنصر واجهة المستخدم الرسومية 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

ويمكن تحديث واجهة المستخدم فقط من قبل ترابط التطبيق الرئيسي. تتم معالجة الاستدعاء المتزامن لهذا الحدث المنفذ التسلسلي وراء الكواليس في موضوع مستقل. كما ذكر ويل، يمكنك استخدام Dispatcher.Invoke إلى الانتظار لتغيير الخاصية عنصر واجهة المستخدم على موضوع UI.

ولكن، منذ كنت تستخدم برنامج الأغذية العالمي، وهناك حل أكثر أناقة واصطلاحي باستخدام الارتباطات. على افتراض أن البيانات التي تتلقاها على المنفذ التسلسلي لديه بعض قيمة كبيرة إلى كائنات عملك، هل يمكن أن يكون هذا الحدث 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 والآن التعامل مع كل من التنظيم عبر موضوع لك التلقائى لذلك لم يكن لديك ما يدعو للقلق الذي هو الاحتجاج موضوع التغييرات UI، محرك ملزم في WPF فقط يجعل من عمل. من الواضح، إذا كان هذا هو مجرد رمي بعيدا مشروع قد لا يكون يستحق كل هذا الجزء الضئيل من التعليمات البرمجية مقدما. ومع ذلك، إذا كنت تفعل الكثير من التواصل المتزامن وتحديث واجهة المستخدم هذه الميزة من برنامج الأغذية العالمي هو المنقذ للحياة الحقيقية ويقضي على فئة كبيرة من البق مشتركة لتطبيقات متعددة الخيوط. نحن نستخدم برنامج الأغذية العالمي في تطبيق مترابطة بشكل كبير القيام بالكثير من الاتصالات TCP وكانت الارتباطات في WPF كبيرة خاصة عندما تكون البيانات القادمة عبر السلك المقصود لتحديث عدة أماكن في واجهة المستخدم وبما انك لا يكون لديك الاختيار خيوط رش جميع أنحاء التعليمات البرمجية.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top