The main reason for COM apartments is to control how objects handle synchronization of calls to their methods. A COM object that implements its own synchronization, or does not need synchronization, can run in a multithreaded apartment (MTA), so any thread in the apartment can access the object at any time. An object that needs synchronization (because its methods use thread sensitive data) but does not provide it itself must run in a single-threaded apartment (STA), which guarantees that only a single thread will access the object regardless of the thread that initialized the call. Windows messages are processed via message queues, which are thread based. A COM object that has a GUI must run on the same thread to ensure that windows messages continue to be handled.
Although .NET provides a whole new paradigm for application development (at the moment it is built over Windows), so much of .NET has more than a passing regard to Windows features. Apartment membership is one of the Windows features that affects .NET development. If your .NET code will ever use a COM object via COM interop, then that code must run in a COM apartment. In some cases the use of COM is hidden: If you use drag and drop with a .NET Control, then you will need a COM apartment because drag and drop uses OLE. Furthermore, because a Control is a GUI element, the COM apartment must be an STA.
The good news is that .NET will detect that you are about to use COM and will call ::CoInitializeEx() for you. The bad news is that .NET will not know about the objects you will use (or more specifically, how you want to access those objects), so it won't know what apartment type to use. By default, .NET will always initialize an MTA. To make .NET initialize an STA, you have to explicitly indicate that this is your intention. There are two ways to do this: First, you can mark a method with the [STAThread] attribute; and second, you can get access to the current thread and change the ApartmentState property to ApartmentState::STA. Once a thread has joined an apartment, it will always remain in that apartment, so the attributeor ApartmentState propertywill only be consulted once for each thread.
Every process has a single thread pool that contains threads managed by the run time. The thread pool is used in several places in .NET. Most often, you use a thread pool thread explicitly through the ThreadPool::QueueUserWorkItem() method. However, if you make a call through .NET Remoting, the object will be called on a thread pool thread. This is very similar to how remote objects are handled in DCOM: RPC maintains a pool of threads in a COM server application to accept COM activation requests. Another situation is when you make asynchronous calls in .NETthe call will be on a pool thread.
Now, heres the interesting part. If you have a .NET method that uses COM, the thread must initialize an apartment. If you want to call the method asynchronously, then the thread will be a pool thread, and you cannot guarantee which thread will be used. Thus, the method that is called asynchronously must be marked with [MTAThread] or explicitly set the ApartmentState to ApartmentState::MTA. If you attempt to set the apartment to STA, then .NET will override you and set the apartment to MTA because the thread is a pool thread and cannot be devoted to accessing a single object. If you want to access an STA object through a method called asynchronously, there is no problem because COM will create the object in an appropriate apartment, and access will be through a proxy. If the method will create many such STA objects that will access each other, proxies impose a performance overhead. To handle this case, your asynchronous method can create a new thread, initialize it to be used in an STA, and then call the COM object on that thread.
Richard Grimes speaks at conferences and writes extensively on .NET, COM, and COM+. He is the author of Developing Applications with Visual Studio .NET (Addison-Wesley, 2002). If you have comments about this topic, Richard can be reached at [email protected].