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