|
dev
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
Threading CallbackI am new to threading/callbacks etc and am having some trouble that I hope someone can help me with. The below class seems to work ok until I handle the CallbackEvent and try to update a control in the main form. I get: "Cross-thread operation not valid: Control 'ListBox1' accessed from a thread other than the thread it was created on." I'm sure the answer is easy but i am having trouble getting my head around it. I have looked at many examples using control.invoke etc but this is just a class not a control. What I would like is for the CallbackEvent to be raised from the main thread not the background thread as i assume this is the issue that is causing the above error. Any help would be appreciated. Thanks My test class: Public Class TestThreadCallback Public Delegate Sub CallbackDelegate(ByVal x As String) Public Event CallbackEvent As CallbackDelegate Private _t As Threading.Thread Sub CallmeBack(ByVal x As String) RaiseEvent CallbackEvent(x) End Sub Sub StartWorker() _t = New Threading.Thread(AddressOf Worker) _t.Start() End Sub Sub StopWorker() _t.Abort() End Sub Sub Worker() Do Threading.Thread.Sleep(2000) CallmeBack("boo") Loop End Sub End Class -- Devron Blatchford Hi Devron,
Yes, this is a common question in .Net winform programming area. Since .Net winform encapsulates the native Win32 controls, which is created with legacy COM technologies. These Win32 controls did not take care of thread-safe in the design, so it is using STA COM threading model. This means that any manipulation to the controls/windows must be occurred on the thread that created the controls/windows. If you call the controls/windows methods from another thread, it may cause some multithreading contention issue with GUI thread. To resolve this issue, as you have found, .Net winform introduced Control.Invoke/BeginInvoke methods for us to marshal the calling. So in your CallbackEvent, any calling to ListBox's methods/properties must be wrapped with ListBox.Invoke/BeginInvoke. Another choice is marshaling the CallmeBack method invoking at first with Control.Invoke/BeginInvoke, then CallmeBack method will execute in GUI thread, so CallbackEvent will execute in GUI thread as well. Then there is no need to do the marshaling now. To get this done, you must find a way to pass the GUI thread's control reference in your class so that your TestThreadCallback class can use Control.Invoke. Below is one sample approach of doing this: Public Class Form1 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim t As New TestThreadCallback t.StartWorker(Me) End Sub Public Class TestThreadCallback Public Delegate Sub CallbackDelegate(ByVal x As String) Public Event CallbackEvent As CallbackDelegate Private _t As Threading.Thread Sub CallmeBack(ByVal x As String) RaiseEvent CallbackEvent(x) End Sub Private m_ctrl As Control Sub StartWorker(ByRef ctrl As Control) m_ctrl = ctrl _t = New Threading.Thread(AddressOf Worker) _t.Start() End Sub Sub StopWorker() _t.Abort() End Sub Sub Worker() Do Threading.Thread.Sleep(2000) If Not m_ctrl Is Nothing And m_ctrl.InvokeRequired Then m_ctrl.Invoke(New CallbackDelegate(AddressOf CallmeBack), New Object() {"boo"}) Else CallmeBack("boo") End If Loop End Sub End Class End Class In this sample, I pass the Form's reference in TestThreadCallback constructor, and then store this reference in a private field m_ctrl. In Worker method, I marshal the CallmeBack calling with m_ctrl.Invoke. It works well on my side. Hope this helps. Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications. Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights. hi Jeffrey,
Thanks alot for your detailed answer, i'm sure it will set me on the right path. I still don't quite understand something. What happens if I want to write a class that has a workerSub in which I want to raise events back to the calling thread as certain situations occur. This class may not be used in a application that has a user interface and therefore no control.invoke. Say I just want to do the following in a console application. Dim withevents _monitor as new TestThreadCallback _monitor.StartWorker Private Sub cbe(ByVal x As String) Handles _monitor.CallbackEvent Console.writeline("Callback") End Sub Is there anyway I can raise the event on the calling thread so the class does not have to have a reference to a control? Something like the timer object maybe: i.e it has no reference to a control. Dim WithEvents t As New Timer t.Interval = 1000 t.Start() Private Sub t_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles t.Tick Console.writeline("Timer Callback") End Sub hope I have explained myself. Thanks again. -- Show quoteDevron Blatchford ""Jeffrey Tan[MSFT]"" wrote: > Hi Devron, > > Yes, this is a common question in .Net winform programming area. > > Since .Net winform encapsulates the native Win32 controls, which is created > with legacy COM technologies. These Win32 controls did not take care of > thread-safe in the design, so it is using STA COM threading model. This > means that any manipulation to the controls/windows must be occurred on the > thread that created the controls/windows. If you call the controls/windows > methods from another thread, it may cause some multithreading contention > issue with GUI thread. > > To resolve this issue, as you have found, .Net winform introduced > Control.Invoke/BeginInvoke methods for us to marshal the calling. So in > your CallbackEvent, any calling to ListBox's methods/properties must be > wrapped with ListBox.Invoke/BeginInvoke. > > Another choice is marshaling the CallmeBack method invoking at first with > Control.Invoke/BeginInvoke, then CallmeBack method will execute in GUI > thread, so CallbackEvent will execute in GUI thread as well. Then there is > no need to do the marshaling now. To get this done, you must find a way to > pass the GUI thread's control reference in your class so that your > TestThreadCallback class can use Control.Invoke. Below is one sample > approach of doing this: > > Public Class Form1 > > Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As > System.EventArgs) Handles Button1.Click > Dim t As New TestThreadCallback > t.StartWorker(Me) > End Sub > > Public Class TestThreadCallback > Public Delegate Sub CallbackDelegate(ByVal x As String) > Public Event CallbackEvent As CallbackDelegate > > Private _t As Threading.Thread > > Sub CallmeBack(ByVal x As String) > RaiseEvent CallbackEvent(x) > End Sub > > Private m_ctrl As Control > Sub StartWorker(ByRef ctrl As Control) > m_ctrl = ctrl > _t = New Threading.Thread(AddressOf Worker) > _t.Start() > End Sub > > Sub StopWorker() > _t.Abort() > End Sub > > Sub Worker() > > Do > Threading.Thread.Sleep(2000) > If Not m_ctrl Is Nothing And m_ctrl.InvokeRequired Then > m_ctrl.Invoke(New CallbackDelegate(AddressOf > CallmeBack), New Object() {"boo"}) > Else > CallmeBack("boo") > End If > > Loop > End Sub > End Class > End Class > > In this sample, I pass the Form's reference in TestThreadCallback > constructor, and then store this reference in a private field m_ctrl. In > Worker method, I marshal the CallmeBack calling with m_ctrl.Invoke. It > works well on my side. > > Hope this helps. > > Best regards, > Jeffrey Tan > Microsoft Online Community Support > ================================================== > Get notification to my posts through email? Please refer to > http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif > ications. > > Note: The MSDN Managed Newsgroup support offering is for non-urgent issues > where an initial response from the community or a Microsoft Support > Engineer within 1 business day is acceptable. Please note that each follow > up response may take approximately 2 business days as the support > professional working with you may need further investigation to reach the > most efficient resolution. The offering is not appropriate for situations > that require urgent, real-time or phone-based interactions or complex > project analysis and dump analysis issues. Issues of this nature are best > handled working with a dedicated Microsoft Support Engineer by contacting > Microsoft Customer Support Services (CSS) at > http://msdn.microsoft.com/subscriptions/support/default.aspx. > ================================================== > This posting is provided "AS IS" with no warranties, and confers no rights. > > Hi Devron,
The "Cross-thread operation not valid: Control 'ListBox1' accessed from a thread other than the thread it was created on" exception should only be thrown with GUI application. This is Winform multithreading rule. If you are not referring GUI controls, there is no need to do the marshaling with Control.Invoke/BeginInvoke. In a normal non-GUI application, your code is responsible for the inter-thread communication, your code should use event, mutext or other synchronization primitives to take care of the thread-safe. CLR will not define any rule for the cross-thread operation for you. Additionally, System.Windows.Forms.Timer.Tick event always occurs in the main GUI thread. HTH. Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications. Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights. Jeffery,
To put my question another way.. how can I get my class to raise the CallbackEvent on the main GUI thread as does the timer.tick event that is not aware of any controls. In the timer tick event i can update a control without the timer object having any prior knowledge of the control. or are you saying that i can not create a class with a worker thread that can raise an event on the GUI thread which within i can manipulate a control or output to a console without implimenting the code in different ways? In your example if I was to use the component/class for a console app would i just pass the control reference as nothing? I hope i am making sence. Thanks again. -- Show quoteDevron Blatchford ""Jeffrey Tan[MSFT]"" wrote: > Hi Devron, > > The "Cross-thread operation not valid: Control 'ListBox1' accessed from a > thread other than the thread it was created on" exception should only be > thrown with GUI application. This is Winform multithreading rule. If you > are not referring GUI controls, there is no need to do the marshaling with > Control.Invoke/BeginInvoke. > > In a normal non-GUI application, your code is responsible for the > inter-thread communication, your code should use event, mutext or other > synchronization primitives to take care of the thread-safe. CLR will not > define any rule for the cross-thread operation for you. > > Additionally, System.Windows.Forms.Timer.Tick event always occurs in the > main GUI thread. > > HTH. > > Best regards, > Jeffrey Tan > Microsoft Online Community Support > ================================================== > Get notification to my posts through email? Please refer to > http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif > ications. > > Note: The MSDN Managed Newsgroup support offering is for non-urgent issues > where an initial response from the community or a Microsoft Support > Engineer within 1 business day is acceptable. Please note that each follow > up response may take approximately 2 business days as the support > professional working with you may need further investigation to reach the > most efficient resolution. The offering is not appropriate for situations > that require urgent, real-time or phone-based interactions or complex > project analysis and dump analysis issues. Issues of this nature are best > handled working with a dedicated Microsoft Support Engineer by contacting > Microsoft Customer Support Services (CSS) at > http://msdn.microsoft.com/subscriptions/support/default.aspx. > ================================================== > This posting is provided "AS IS" with no warranties, and confers no rights. > > Hi Devron,
I understand your main concern better now. However, it is impossible for a method code(in thread #2) to invoke another method in main GUI thread(thread #1) without getting any reference to controls in main GUI thread. The key problem is that the information is not enough. There is no information in the worker thread #2 of which thread is main GUI thread. So in thread #2, without the control's reference, the code in thread #2 has no information regarding which thread you want to place the call, so it is impossible to make the call. Below is some internal information: 1. Control.Invoke internally just check if the control reference's creating thread is the same as the current invoking thread(In our scenario, it is worker thread #2), if they are not the same, Control.Invoke will p/invoke PostMessage win32 API to make the main GUI thread call. In this situation, since we have the control reference, we can get the main GUI thread id, so we have enough information to call PostMessage API. 2. System.Windows.Forms.Timer class always fires Tick event in the GUI thread, this is because System.Windows.Forms.Timer class can not be used in a non-GUI thread. It internally uses SetTimer and intercept WM_TIMER message in GUI thread to work. If you create it in a non-GUI thread, it will fail to work. Hope this is clear. Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications. Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights. Also note, an event is fundamentally just a delegate in wrapper. So when
you call the event in thread#2 it runs the code in the context of thread#2, not thread#1. That is why you still need to Invoke() onto the GUI thread if you want to update a control. The Winforms Timer does this for you, so the callback code in that case actually runs on the UI thread (i.e. it does the Invoke for you in the background). If your not using a UI (i.e. console) then you don't need to invoke it on thread#1, thread#2 will just run the delegate. hth -- Show quoteWilliam Stacey [MVP] "Devron Blatchford" <fred@nospam.nospam> wrote in message http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notifnews:627AE3E4-8C49-40CA-B9DF-0C6D6E375104@microsoft.com... | Jeffery, | | To put my question another way.. | | how can I get my class to raise the CallbackEvent on the main GUI thread as | does the timer.tick event that is not aware of any controls. In the timer | tick event i can update a control without the timer object having any prior | knowledge of the control. | or | are you saying that i can not create a class with a worker thread that can | raise an event on the GUI thread which within i can manipulate a control or | output to a console without implimenting the code in different ways? | | In your example if I was to use the component/class for a console app would | i just pass the control reference as nothing? | | I hope i am making sence. | | Thanks again. | | | | | | | | | -- | Devron Blatchford | | | ""Jeffrey Tan[MSFT]"" wrote: | | > Hi Devron, | > | > The "Cross-thread operation not valid: Control 'ListBox1' accessed from a | > thread other than the thread it was created on" exception should only be | > thrown with GUI application. This is Winform multithreading rule. If you | > are not referring GUI controls, there is no need to do the marshaling with | > Control.Invoke/BeginInvoke. | > | > In a normal non-GUI application, your code is responsible for the | > inter-thread communication, your code should use event, mutext or other | > synchronization primitives to take care of the thread-safe. CLR will not | > define any rule for the cross-thread operation for you. | > | > Additionally, System.Windows.Forms.Timer.Tick event always occurs in the | > main GUI thread. | > | > HTH. | > | > Best regards, | > Jeffrey Tan | > Microsoft Online Community Support | > ================================================== | > Get notification to my posts through email? Please refer to | > Show quote | > ications. | > | > Note: The MSDN Managed Newsgroup support offering is for non-urgent issues | > where an initial response from the community or a Microsoft Support | > Engineer within 1 business day is acceptable. Please note that each follow | > up response may take approximately 2 business days as the support | > professional working with you may need further investigation to reach the | > most efficient resolution. The offering is not appropriate for situations | > that require urgent, real-time or phone-based interactions or complex | > project analysis and dump analysis issues. Issues of this nature are best | > handled working with a dedicated Microsoft Support Engineer by contacting | > Microsoft Customer Support Services (CSS) at | > http://msdn.microsoft.com/subscriptions/support/default.aspx. | > ================================================== | > This posting is provided "AS IS" with no warranties, and confers no rights. | > | > Hi Devron,
Additionally, if this information is what you want to know, the event is always fired in the same thread as the method fired this event, there is no syntax in .Net language like Control.Invoke/BeginInvoke to fire an event in another thread. So the recommended workaround is wrapping the code that is going to fire the event in a method, then we should marshal this method with Control.Invoke/BeginInvoke. Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications. Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights. Hi Devron,
Have you reviewed my last 2 replies to you? Does it make sense to you? If you have any concern, please feel free to tell me, thanks. Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications. Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights. |
|||||||||||||||||||||||