COM Interop Threading
COM Interop Threading
I've now had the (dis-)pleasure of working with .NET COM Interop in a number of different situations. If this can be avoided and the code kept completely on the managed side, I highly recommend it. The two paradigms are quite different and the Interop Runtime allows them to play together but very loosly coupled. That said, if COM must be interacted with, there is an issue which I've run into a number of times. Many COM objects need to be run in an STA ApartmentState, but many of the modern environments (ASP.NET, .NET Event Handling) are running in MTA ApartmentState. In my experience, when COM is invoked from a thread in MTA, the error message given may have nothing to do with the actual issue.
To back up, one must first understand MTA and STA. There are many MSDN articles about this so I'll keep it brief. MTA stands for "Multi-Threaded Apartment" (or sometimes "free-threaded") and STA stands for "Single-Threaded Apartment". COM objects which are not thread safe (e.g. SQL Server 2000 SQLXML Extensions, OWC 11), must be invoked and operated on an STA thread.
The problem is that when a .NET System.Thread is instantiated, its ApartmentState property can only be set once. So even though it has a read/write property, it ignores any but the first. This means you must instantiate a new Thread, set its ApartmentState, and execute on that. The following code is a pattern which I've found works around the issue well:private [...variables...]
[STAThread]
public void DoWork([...parameters...])
{
this.[...variables...] = [...parameters...];
// try setting the ApartmentState
System.Threading.Thread.CurrentThread.ApartmentState = System.Threading.ApartmentState.STA;
if (System.Threading.Thread.CurrentThread.ApartmentState != System.Threading.ApartmentState.STA)
{
// Thread.ApartmentState can only be set once per thread. So must use a new thread.
if (this.StaThread == null)
this.StaThread = new Thread(new ThreadStart(this.Execute));
this.StaThread.ApartmentState = ApartmentState.STA;
this.StaThread.Start();
this.StaThread.Join();
}
else
{
this.Execute();
}
}
[STAThread]
private void Execute()
{
if (System.Threading.Thread.CurrentThread.ApartmentState != System.Threading.ApartmentState.STA)
throw new InvalidOperationException("This method has to execute in an STA ApartmentState.");
// do the work using this.[...variables...]
}
Note: The [STAThread] attribute is my old futile attempt to set these thread states. Unfortunately they were already set. You'll recognize this attribute from the Main method on any windows client apps you've built.
Also note that this pattern only creates a new thread if it has to. Threading can seriously complicate an otherwise simple workflow. This pattern isn't meant to actually work with threads in their intended use. In fact, you'll notice that the Thread.Join method actually will block the MTA thread until the STA thread completes. What you see here is typically wasteful since now you are using two threads to do the work of one. But in this case, we had bigger problems. Ideally, if the Interop code could peacefully execute asynchronously we could remove the Thread.Join and allow it to complete in its own time.
Finally, one should note that ASP.NET has a Page option which allows it to be executed in STA mode. This page directive looks like this: