What are these "Threading Models" and why do I care?

Somehow it seems like it’s been “Threading Models” week, another example of “Blogger synergy”. I wrote this up for internal distribution to my group about a year ago, and I’ve been waiting for a good time to post it. Since we just hit another instance of the problem in my group yesterday, it seemed like a good time.

 

So what is this thing called a threading model anyway?

Ok. So the COM guys had this problem. NT supports multiple threads, but most developers, especially the VB developers at which COM/ActiveX were targeted are totally terrified by the concept of threading. In fact, it’s very difficult to make thread-safe VB (or JS) applications, since those languages don’t support any kind of threading concepts. So the COM guys needed to design an architecture that would allow for supporting these single-threaded objects and host them in a multi-threaded application.

The solution they came up was the concept of apartments. Essentially each application that hosts COM objects holds one or more apartments. There are two types of apartments, Single Threaded Apartments (STAs) and Multi Threaded Apartments (MTAs). Within a given process there can be multiple STA’s but there is only one MTA.

When a thread calls CoInitializeEx (or CoInitialize), the thread tells COM which of the two apartment types it’s prepared to host. To indicate that the thread should live in the MTA, you pass the COINIT_MULTITHREADED flag to CoInitializeEx. To indicate that the thread should host an STA, either call CoInitialize or pass the COINIT_APARTMENTTHREADED flag to CoInitializeEx.

A COM object’s lifetime is limited to the lifetime of the apartment that creates the object. So if you create an object in an STA, then destroy the apartment (by calling CoUninitialize), all objects created in this apartment will be destroyed.

Single Threaded Apartment Model Threads

When a thread indicates that it’s going to be in single threaded apartment, then the thread indicates to COM that it will host single threaded COM objects. Part of the contract of being an STA is that the STA thread cannot block without running a windows message pump (at a minimum, if they block they must call MsgWaitForSingleObject – internally, COM uses windows messages to do inter-thread marshalling).

The reason for this requirement is that COM guarantees that objects will be executed on the thread in which they were created regardless of the thread in which they’re called (thus the objects don’t have to worry about multi-threading issues, since they can only ever be called from a single thread). Eric mentions “rental threaded objects”, but I’m not aware of any explicit support in COM for this.

 

Multi Threaded Apartment Model Threads

Threads in the multi threaded apartment don’t have any restrictions – they can block using whatever mechanism they want. If COM needs to execute a method on an object and no thread is blocked, then COM will simply spin up a new thread to execute the code (this is particularly important for out-of-proc server objects – COM will simply create new RPC threads to service the object as more clients call into the server).

How do COM objects indicate which thread they work with?

When an in-proc COM object is registered with OLE, the COM object creates the following registry key:

            HKCR\CLSID\{<Object class ID>}\InprocServer32

The InprocServer32 tells COM which DLL hosts the object (in the default value for the key), and via the ThreadingModel value tells COM the threading model for the COM object.

 

There are essentially four legal values for the ThreadingModel value. They are:

Apartment

Free

Both

Neutral

Apartment Model objects.

When a COM object is marked as being an “Apartment” threading model object, it means that the object will only run in an STA thread. All calls into the object will be serialized by the apartment model, and thus it will not have to worry about synchronization.

Free Model objects.

When a COM object is marked as being a “Free” threading model object, it means that the object will run in the MTA. There is no synchronization of the object. When a thread in an STA wants to call into a free model object, then the STA will marshal the parameters from the STA into the MTA to perform the call. 

Both Model objects.

The “Both” threading model is an attempt at providing the best of both worlds. An object that is marked with a threading model of “Both” takes on the threading model of the thread that created the object. 

Neutral Model objects.

With COM+, COM introduced the concept of a “Neutral” threading model. A “Neutral” threading model object is one that totally ignores the threading model of its caller.

COM objects declared as out-of-proc (with a LocalServer32=xxx key in the class ID.) are automatically considered to be in the multi-threaded apartment (more about that below).

It turns out that COM’s enforcement of the threading model is not consistent. In particular, when a thread that’s located in an STA calls into an object that was created in the MTA, COM does not enforce the requirement that the parameters be marshaled through a proxy object. This can be a big deal, because it means that the author of COM objects can be lazy and ignore the threading rules – it’s possible to create a COM object in that uses the “Both” threading model and, as long as the object is in-proc, there’s nothing that’ll check to ensure you didn’t violate the threading model. However the instant you interact with an out-of-proc object (or call into a COM method that enforces apartment model checking), you’ll get the dreaded RPC_E_WRONG_THREAD error return. The table here describes this in some detail.

What about Proxy/Stub objects?

Proxy/Stub objects are objects that are created by COM to handle automatically marshaling the parameters of the various COM methods to other apartments/processes. The normal mechanism for registering Proxy/Stub objects is to let COM handle the registration by letting MIDL generate a dlldata.c file that is referenced during the proxy DLL’s initialization.

When COM registers these proxy/stub objects, it registers the proxy/stub objects with a threading model of “Both”. This threading model is hard-coded and cannot be changed by the application.

What limitations are there that I need to worry about?

The problem that we most often see occurs because of the Proxy/Stub objects. Since the proxy/stub objects are registered with a threading model of “Both”, they take on the threading model of the thread that created the object. So if a proxy/stub object is created in a single threaded apartment, it can only be executed in the apartment that created it. The proxy/stub marshaling routines DO enforce the threading restriction I mentioned above, so applications learn about this when they unexpectedly get a RPC_E_WRONG_THREAD error return from one of their calls. On the server side, the threading model of the object is set by the threading model of the caller of CoRegisterClassObject. The good news is that the default ALT 7.1 behavior is to specify multi-threaded initialization unless otherwise specified (in other words, the ATL header files define _ATL_FREE_THREADED by default.

How do I work around these limitations?

Fortunately, this problem is a common problem, and to solve it COM provides a facility called the “Global Interface Table”. The GIT is basically a singleton object that allows you to register an object with the GIT and it will then return an object that can be used to perform the call from the current thread. This object will either be the original object (if you’re in the apartment that created the object) or it will be a proxy object that simply marshals the calls into the thread that created the object.

If you have a COM proxy/stub object (or you use COM proxy/stub objects in your code), you need to be aware of when you’ll need to use the GIT to hold your object.

Use the GIT, after you’ve called CoCreateInstance to create your COM object, call IGlobalInterfaceTable::RegisterInterfaceInGlobal to add the object to the global interface table. This will return a “cookie” to you. When you want to access the COM object, you first call IGlobalInterfaceTable::GetInterfaceFromGlobal to retrieve the interface. When you’re done with the object, you call IGlobalInterface::RevokeInterfaceFromGlobal.

In our case, we didn’t feel that pushing the implementation details of interacting with the global interface table to the user was acceptable, so we actually wrote an in-proc object that wraps our out-of-proc object. 

Are there other problems I need to worry about?

Unfortunately, yes. Since the lifetime of a COM object is scoped to the lifetime of the apartment that created the object, this means that when the apartment goes away, the object will go away. This will happen even if the object is referenced from another thread. If the object in question is a local object, this really isn’t that big a deal since the memory backing the object won’t go away. If, however the object is a proxy/stub object, then the object will be torn down post-haste. The global interface table will not help this problem, since it will remove all the entries in the table that were created in the apartment that’s going away.

Additional resources:

The MSDN article Geek Speak Decoded #7 (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dngeek/html/geekthread.asp) also has some detail on how this stuff works (although it’s somewhat out-of-date).