Channels ▼

More on COM Objects and Threads in .NET

[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:

private void WorkItem(object o)
		SimpleSTAClass c = 
			new SimpleSTAClass();
	catch(Exception e )
		System.Windows.Forms.MessageBox.Show( e.Message );

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 [email protected].

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.