Discussion:
STA and MTA in VB.NET and Multithreading - MSFT pls explain in HOW
(too old to reply)
herbert
2004-12-05 15:12:25 UTC
Permalink
Being a VB.NET programmer I wandered what is MTA and STA is all about for my
daily life, cause in my 30+ books about .NET its only mentioned it in three
lines.
However now I have a problem using events to synch multiple threads.

Im refering to a code snipped in MS-PRESS NEtwork programming for the .NET
Framework, ISBN ...1959-X, page 58 / 59.

The text goes about using Events to sync multiple threads in the threadpool
with the foreground thread and the code (yes, you guess it) then shows how to
sync one thread.

So I completed the example (see below). I use a console app, as I know that
there are problems with Windows forms in this case. I cannot declare the
callback routine shared cause a console module does not allow it (why not, by
the way?)

When I run the app, I get a message which (translated to English) says
something like: "WaitAll for several handles not supported in STA."

That's true as the app runs correctly with a single threadpool thread. (code
below)

So what's happening inside?
How do I make my console module MTA?
Where is a comprehensive, all-in-one explanation for the guy in the basement
IT dept?

thanks herbert

Imports System.Threading

Module Module1

Sub Main()

'this delegate wraps the method to be executed in the threadpool
Dim CallbackMethod1 As WaitCallback = New WaitCallback(AddressOf
MyThreadPoolMethod1)
'Dim CallbackMethod2 As WaitCallback = New WaitCallback(AddressOf
MyThreadPoolMethod2)
'Dim CallbackMethod3 As WaitCallback = New WaitCallback(AddressOf
MyThreadPoolMethod3)

'create an array of wait handle objects to wait on
Dim WaitHandleArray(2) As WaitHandle 'allow three threads in
threadpool to synchronize with foreground thread

'Assign a manual reset event object to the array (one for each
thread in the pool)
WaitHandleArray(0) = New ManualResetEvent(False)
'WaitHandleArray(1) = New ManualResetEvent(False)
'WaitHandleArray(2) = New ManualResetEvent(False)

'use the threadpool
'pass the manual reset event object to the wait callback method
(this is the method executed in the threadpool)
Console.WriteLine("queue tasks to threadpool.")
ThreadPool.QueueUserWorkItem(CallbackMethod1, WaitHandleArray(0))
'ThreadPool.QueueUserWorkItem(CallbackMethod2, WaitHandleArray(1))
'ThreadPool.QueueUserWorkItem(CallbackMethod3, WaitHandleArray(2))

'Wait for the callback method to complete
Console.WriteLine("wait for all three threads to complete.")
WaitHandle.WaitAll(WaitHandleArray)

Console.WriteLine("All three threads completed.")
Console.ReadLine()
End Sub

Sub MyThreadPoolMethod1(ByVal State As Object)

'assume a manual reset event object was passed in the State parameter
Dim MRE As ManualResetEvent = CType(State, ManualResetEvent)

'do something useful here
Thread.Sleep(1000)

'signal the manual reset event object when the callback routine is
finished
MRE.Set()
End Sub

Sub MyThreadPoolMethod2(ByVal State As Object)

'assume a manual reset event object was passed in the State parameter
Dim MRE As ManualResetEvent = CType(State, ManualResetEvent)

'do something useful here
Thread.Sleep(1000)

'signal the manual reset event object when the callback routine is
finished
MRE.Set()
End Sub

Sub MyThreadPoolMethod3(ByVal State As Object)

'assume a manual reset event object was passed in the State parameter
Dim MRE As ManualResetEvent = CType(State, ManualResetEvent)

'do something useful here
Thread.Sleep(1000)

'signal the manual reset event object when the callback routine is
finished
MRE.Set()
End Sub

End Module
herbert
2004-12-05 15:17:01 UTC
Permalink
yes, it works when setting <MTAThread> attribute.
However why? When is it correct or dangerous to switch between STA and MTA
and back?
Or is it "don't think until your managed code crashes"?

thanks herbert
Richard Blewett [DevelopMentor]
2004-12-05 18:44:53 UTC
Permalink
OK herbert it works like this:

COM is a binary standard. This means that different languages create COM components in native code that agree to behave as the standard states. The problem is that different langauges have different capabilities with regard to multthreading and thread affinity (which threads the code should run on). C++ code can be written such that it is thread safe and that it doesn't care about which thread it is called on. VB 6 COM objects must always be called on the thread that created them. The problem is that if a C++ COm object has a reference to a VB 6 object, how do you stop just any thread that calls the C++ object ending up calling the VB 6 object.

The solution to this problem was the apartment abstraction. Different COM classes can declare there threading requirements via the ThreadingModel value under the InprocServer32 key in the registry. There are 3 apartment types that objects can live in, however, for this discussion only two matter, The Multithreaded Apartment (MTA) and Single Threaded Apartments (STA). There is one MTA per process and there can be many STAs. VB objects must live in an STA as must all ActiveX controls (although this is a limitation of the underlying windowing API). C++ objects can live an any apartment.

Now, threads must initialize the COM library when they are going to perform COM work. They can either initialize themselves to run in the MTA or in their own STA. Now when a thread is initialized into an STA, a hidden window is created and calls to objects living in that STA come in via a windows message queue. So unless the thread is processing a windows message pump, COM objects in that STA are essentially locked out as they cannot receive calls.

WaitHandle.WaitAll performs a blocking wait. This means that if you call it on an STA thread that should be running a message pump, suddenly it cannot pump messages anymore. If one of the COM object's in that STA is holding on to one of the locks that WaitAll is waiting on you have a deadlock. So you are not allowed to call WaitHandle.WaitAll on an STA thread - thats why you get an InvalidOperationException unless you mark the thread with the MTAThread attribute as VB.NET will set the thread to be an STA thread otherwise. And thats why it works if you use the MTAThread attribute.

However, using the STATHread and MTAThread attributes simply set the ApartmentState on the thread. The thread does not initialize the COM library unless it actually performs COM interop. Blocking the thread *only* matters if it has entered an STA. The problem is that WaitHandle.WaitAll only appears to check the ApartmentState of the thread, not whether it has initialized the COM library which is a bit sad.

But anyway, thats the explanation of what you are seeing

Regards

Richard Blewett - DevelopMentor
http://www.dotnetconsult.co.uk/weblog
http://www.dotnetconsult.co.uk

yes, it works when setting <MTAThread> attribute.
However why? When is it correct or dangerous to switch between STA and MTA
and back?
Or is it "don't think until your managed code crashes"?

thanks herbert
Valery Pryamikov
2004-12-05 22:57:12 UTC
Permalink
Ah, these STA/MTA questions does bring nostalgic feeling... don't they :-).

Regards,
-Valery.
http://www.harper.no/valery
Post by Richard Blewett [DevelopMentor]
COM is a binary standard. This means that different languages create COM
components in native code that agree to behave as the standard states. The
problem is that different langauges have different capabilities with
regard to multthreading and thread affinity (which threads the code should
run on). C++ code can be written such that it is thread safe and that it
doesn't care about which thread it is called on. VB 6 COM objects must
always be called on the thread that created them. The problem is that if a
C++ COm object has a reference to a VB 6 object, how do you stop just any
thread that calls the C++ object ending up calling the VB 6 object.
The solution to this problem was the apartment abstraction. Different COM
classes can declare there threading requirements via the ThreadingModel
value under the InprocServer32 key in the registry. There are 3 apartment
types that objects can live in, however, for this discussion only two
matter, The Multithreaded Apartment (MTA) and Single Threaded Apartments
(STA). There is one MTA per process and there can be many STAs. VB objects
must live in an STA as must all ActiveX controls (although this is a
limitation of the underlying windowing API). C++ objects can live an any
apartment.
Now, threads must initialize the COM library when they are going to
perform COM work. They can either initialize themselves to run in the MTA
or in their own STA. Now when a thread is initialized into an STA, a
hidden window is created and calls to objects living in that STA come in
via a windows message queue. So unless the thread is processing a windows
message pump, COM objects in that STA are essentially locked out as they
cannot receive calls.
WaitHandle.WaitAll performs a blocking wait. This means that if you call
it on an STA thread that should be running a message pump, suddenly it
cannot pump messages anymore. If one of the COM object's in that STA is
holding on to one of the locks that WaitAll is waiting on you have a
deadlock. So you are not allowed to call WaitHandle.WaitAll on an STA
thread - thats why you get an InvalidOperationException unless you mark
the thread with the MTAThread attribute as VB.NET will set the thread to
be an STA thread otherwise. And thats why it works if you use the
MTAThread attribute.
However, using the STATHread and MTAThread attributes simply set the
ApartmentState on the thread. The thread does not initialize the COM
library unless it actually performs COM interop. Blocking the thread
*only* matters if it has entered an STA. The problem is that
WaitHandle.WaitAll only appears to check the ApartmentState of the thread,
not whether it has initialized the COM library which is a bit sad.
But anyway, thats the explanation of what you are seeing
Regards
Richard Blewett - DevelopMentor
http://www.dotnetconsult.co.uk/weblog
http://www.dotnetconsult.co.uk
yes, it works when setting <MTAThread> attribute.
However why? When is it correct or dangerous to switch between STA and MTA
and back?
Or is it "don't think until your managed code crashes"?
thanks herbert
Richard Blewett [DevelopMentor]
2004-12-05 23:22:00 UTC
Permalink
LOL, Valery - must be about 6 years since those heady days of the DCOM list ;-). Funny how some ridiculas levels of detail stick in your mind.

Regards

Richard Blewett - DevelopMentor
http://www.dotnetconsult.co.uk/weblog
http://www.dotnetconsult.co.uk

Ah, these STA/MTA questions does bring nostalgic feeling... don't they :-).

Regards,
-Valery.
http://www.harper.no/valery

Loading...