Threading Callback

G

Guest

Hi there,

I 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
 
J

Jeffrey Tan[MSFT]

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.
 
G

Guest

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.

--
Devron Blatchford


"Jeffrey Tan[MSFT]" said:
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.
 
J

Jeffrey Tan[MSFT]

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.
 
G

Guest

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.
 
J

Jeffrey Tan[MSFT]

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.
 
W

William Stacey [MVP]

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

--
William Stacey [MVP]

| 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
| >
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.
| >
| >
 
J

Jeffrey Tan[MSFT]

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.
 
J

Jeffrey Tan[MSFT]

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top