More on COM Objects and Threads in .NET

By Mark Baker, June 07, 2005

If you need to do COM with legacy objects that are not thread safe and you want to access COM objects from multiple threads, you can't use the ThreadPool

[Part 2 of 2] In the previous column, I left you in limbo with why the [STAThread]
attribute on your main method (or whatever entry point method you
use) isnt enough to guarantee COM Interop will do the right thing if youre
multithreading. Now Ill wrap up the mystery.

The [STAThread] attribute is really cool and is a prime example of why,
at times, COM Interop can seem like magic. This single attribute tells .NET
that if the application interacts with COM objects, it should do so as
STAs (single threaded apartment, a.k.a. apartment model). This is the
most common way to interact with COM for the majority of developers; in particular,
any legacy OLE objects required STA. To be really helpful, the Visual Studio
.NET App Wizard will automatically add this attribute to any applications it
generates. If you remove the attribute, COM Interop still works, but it reverts
to MTA model (multithreaded apartment). You could also explicitly set the model
to MTA by changing the [STAThread] attribute to [MTAThread]. But
most developers interacting with COM wouldnt bother. And its this
default behavior of .NET to favor MTAs rather than STAs that will trip you up.

Recall that the most straightforward (and recommended) way for .NET developers
to create secondary (worker) threads in their application is to use ThreadPool.QueueUserWorkItem.
This dandy little method makes it a snap to spin off a thread efficientlywhen
a thread terminates or completes its operation, the ThreadPool adds
it back to its container of available threads rather than actually destroying
it. This is much more efficient than using the Thread class directly
and makes sense most of the timean exception is when things like thread
prioritization need to be specified.

So to use ThreadPool.QueueUserWorkItem, youd write some code that
looks like this:

public void DoStuff()<br>{<br> ThreadPool.QueueUserWorkItem( new WaitCallback(WorkItem),1 );<br> <br> //.. do more stuff in primary thread.<br>}

In the call to QueueUserWorkItem, I create an instance of the WaitCallback
class that wraps an ordinary method in my class named WorkItem. This
method is where I begin the work for my secondary thread. I'm passing it a parameter
with the value of "1. Actually, QueueUserWorkItem is flexibleyou
can pass any .NET object you wish to the method and that object will be dutifully
passed to the method you passed into WaitCallback. Here is how that code
looks:

The method takes a single parameter of type object, which is really
the parameter I passed into QueueUserWorkItem earlier. The body of the
WorkItem method could be elaborate if need be, but in my case, I simply
create an instance of an STA object that I added as a reference to my project:

All is well, yes? Actually, no. This is where you can get bitten. This code
will compile and run just fine; however, as my code gets more complex, it starts
acting strangely as more threads interact with this legacy COM object. But the
code is set to use STAThread, so how can this be?

Looking more closely at the QueueUserWorkItem method documentation,
theres no mention of anything about COM. Hmm. Moving up the chain of documentation
to a more general description of thread pools, we come across this gem:

There are several scenarios in which it is appropriate to create and manage your own threads instead of using the ThreadPool. You should do so:

If you require a task to have a particular priority.

If you have a task that might run a long time (and therefore block other tasks).

If you need to place threads into a single-threaded apartment (all ThreadPool
threads are in the multithreaded apartment).

If you need to have a stable identity associated with the thread. For example, you might want to use a dedicated thread to abort that thread, suspend it, or discover it by name.

The third item is the culprit. All threads created in the ThreadPool are MTA
threads, not STA threads. Yikes. If you recall from COM, all threads must
initialize COM by calling CoInitialize or CoInitializeEx explicitly.
Not doing so meant COM basically didn't work in that thread. In .NET, the CLR
and COM Interop handle this for you invisibly. Trying to be helpful, .NET says
we'll use our default COM model of MTA (as I mentioned earlier, it does if you
leave off the [STAThread] attribute from the entry point method of your
application). So you now have a case where your application's main thread is
using STA and the worker thread is using MTA. Very ugly if the COM object states
it's not thread safe.

You might be thinking you could just override the ApartmentState property
of the Thread object created from the ThreadPool. Besides being a bad
idea, it doesnt work. Doing something like this as the first line of code
in method WorkItem:

Thread.CurrentThread.ApartmentState = ApartmentState.STA;

results in basically nothingthe thread simply ignores the attempt to
override the apartment state. A good thing, because these threads are in a thread
pool and forgetting to reset the state back to MTA could really mess up other
attempts to use the thread pool where MTA threads were expected.

So, the net of all this is that if you need to do COM with legacy objects that
are not thread safe and you want to access COM objects from multiple
threads, you cant use the ThreadPool. I suggest reading the MSDN Library
paragraph describing the situations where you shouldnt use a ThreadPool
carefully. If you do a lot of work with Threads, print it out and paste it on
your wall. Youll be glad you did when working with .NET and COM.

Mark M. Baker is the Chief of Research & Development at BNA Software located
in Washington, D.C.
Do you have a Windows development question? Send it to markbaker-winqa8364@mailblocks.com.