Multithreading and COM Apartments

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Seven.  Advanced .NET to COM Interop


In a multithreaded environment, COM objects use Apartments to synchronize access to resources that may be shared by multiple threads. Managed code objects use synchronization regions and synchronization primitives like monitors , mutexes , and locks for the same purpose. Yes, you read that correctly. Assuming that you are not using COM Interop (or Windows Forms), your managed code does not use Apartments. For those of you who never quite grasped the concept of COM Apartments, this is probably cause for celebration .

Note

I personally think that Microsoft dropping the Apartment concept in the .NET Framework is a tacit admission by them that the "fix"COM Apartmentswas worse than the original problemthe need to synchronize access to resources in a multithreaded environment.


But before you pop the champagne , keep in mind that, if you are reading this book, you probably want to use COM components in your managed code applications, and, unfortunately , if you do use COM components, your managed code application will use Apartments.

Before a thread can instantiate a COM object, it must enter an Apartment. The way I like to think of it is that entering an Apartment is just the thread's way of announcing to the world (or at least to the COM objects that it will subsequently create) one of two things: whether the thread is primarily an independent thread that will not need to share resources extensively with other threads running in the same process or it is one of a group of threads in a multithreaded environment. An independent thread should enter an STA by calling CoInitializeEx as follows :

 CoInitializeEx(NULL,COINIT_APARTMENT_THREADED) 

If the thread is one thread in a multithreaded environment, it should enter the MTA (there's only one in each process) by calling CoInitializeEx as follows:

 CoInitializeEx(NULL,COINIT_MULTI_THREADED) 

In-Process COM objects specify which type of threading environment they can operate in by adding a registry value called ThreadingModel beneath their InprocServer32 subkey . The object can specify one of the five values shown in Table 7-9.

Note

Out-of-process (executable) servers do not use the ThreadingModel registry key because they do not interact directly with their client's threads because the client runs in a different process.


Table 7-9. Threading models for COM objects

Threading Model Value

Description

Single

Objects of this class are pathologically un-thread safe. Not only are the objects not thread safe, but the class factory for the class and the DllGetClassObject and DllCanUnloadNow functions for the server are also not thread safe. A single thread must call all of the code in this server.

Apartment

Objects of this class do not protect their per-instance state from multithreading problems. Access to each instance must be limited to a single thread. However, the per-class state for this class and the class factory and the DllGetClassObject and DllCanUnloadNow functions in the classes' server are thread safe.

Free

COM objects of this class protect their per-instance state and per-class state from multithreading problems using thread synchronization primitives, such as critical sections or mutexes. These objects are safe to use in a multithreaded environment.

Both

Object of this class will be instantiated in the same apartment as their client so they can, for maximum performance, execute on their caller's thread. Because they may run in a multi-threaded environment, these classes should protect their per-instance state and per-class state from multi-threading problems.

Neutral

Objects of this class will also execute on their caller's thread, but they will reside in their own apartment, the Thread Neutral Apartment (TNA), rather then residing in their caller's apartment. Because any thread (STA or MTA) in a process can enter the TNA at any time, objects marked as Neutral must either use thread-synchronization primitives internally to make themselves thread safe, or they can use the thread synchronization service provided by the COM+ runtime.

If a COM object was not designed to be thread safe, it will typically use the Apartment (STA) for its ThreadingModel. Some older objects use the more restrictive Single ThreadingModel. If the COM object was designed to be thread safe, it can use either the Free, Both, or Neutral values for its ThreadingModel. To some extent, Free is almost a legacy value because most objects that can run in an MTA can also run in an STA, so Both is usually used nowadays where Free might have been used in the past. The Neutral threading model is primarily useful for COM+ objects.

After a thread has specified what sort of environment it is a part of, that is, an single-threaded environment (STA) or one thread in a multithreaded environment (MTA) and an object has specified what sort of threading environment it can operate in by setting the ThreadingModel registry value, it is fairly easy for the COM runtime to ensure that COM objects are only instantiated in an environment that they can safely operate in. For instance, if you instantiate a COM object that has the Apartment ThreadingModel value, that is, the object can only be instantiated in an STA, and the creating thread is an MTA thread, the COM runtime will create an STA (and potentially another thread) for the object and set up a proxy that marshals method calls between the object's STA thread and the calling MTA thread. However, if the creating thread is an STA thread, the COM runtime recognizes that the object is compatible with its creating thread, and it allows the calling thread to call the object directly without going through a proxy.

A fairly large performance penalty is associated with cross-apartment marshaling; it's almost as bad as cross-process marshaling. Therefore, the basic goal when using COM is to avoid cross-apartment marshaling to the greatest extent possible. That means you should try to make sure that an object is instantiated by a compatible thread. Table 7-10 shows the compatibility matrix between COM Apartments and threading models.

Table 7-10. Compatibility matrix between COM objects and threading models

Threading Model

MTA

Primary STA

Other STA

Single

Marshaled

Direct

Marshaled

Apartment

Marshaled

Direct

Direct

Free

Direct

Marshaled

Marshaled

Both

Direct

Direct

Direct

Neutral

Marshaled

Marshaled

Marshaled

Marshaled means that cross-apartment marshaling is required. Direct means that no marshaling is required.

If you are using COM objects from a managed code application, you will want to make sure that the apartment that the CLR creates is compatible with the threading model of the COM object so that you avoid cross-apartment marshaling. The basic goals are simple and can be summarized as follows:

  • If you are using STA COM object(s) (Apartment ThreadingModel), you want to make sure that your managed code thread enters an STA.

  • If you are using MTA-only COM object(s) (Free ThreadingModel), you want to make sure that your managed code thread enters the MTA.

  • If you are using COM object(s) that can exist in either type of Apartment (Both ThreadingModel) or objects that are thread neutral (Neutral ThreadingModel), your managed code thread can enter either type of Apartment.

You can control the Apartment that your object enters using the ApartmentState property on the System.Threading.Thread class. You can access the Apartment state of the current thread using the following code:

 ApartmentState aptState; aptState=Thread.CurrentThread.ApartmentState; 

CurrentThread is a static, read-only property on the System.Threading.Thread class, which returns a Thread object that represents the currently running thread. The ApartmentState property is typed as System.Threading.ApartmentState , which is an enumeration that has three possible values:

  • MTA The thread will create and enter an MTA.

  • STA The thread will create and enter an STA.

  • Unknown The apartment state has not been set (the initial value).

Table 7-11 summarizes the calls that your managed thread will make to the CoInitializeEx COM Apartment initialization function based on the various settings of the ApartmentState property.

Table 7-11. COM Apartment initialization for various ApartmentState values

ApartmentState Enumeration Value

COM Apartment Initialization

MTA

CoInitializeEx(NULL,COINIT_MULTIHREADED)

STA

CoInitializeEx(NULL,APARTMENTTHREADED)

Unknown

CoInitializeEx(NULL,COINIT_MULTIHREADED)

Keep in mind that the current value in ApartmentState does not necessarily represent the current Apartment that the object resides in. Rather, it represents the Apartment that the thread will enter if you perform some operation that requires you to enter an Apartment, such as instantiating a COM object. When you create a managed thread (I will show you how to do this shortly), it starts out with an ApartmentState of Unknown. You can only set the ApartmentState when the thread is in the Running or Unstarted states, and you cannot change the value of the ApartmentState after a call has been made to a COM component because a thread can only enter an Apartment once. Any calls to set the ApartmentState are ignored after the thread's Apartment has been initialized . If you instantiate a COM object without explicitly setting the ApartmentState, the CLR will call CoInitializeEx(NULL,COINIT_MULTIHREADED) on the current thread to enter the MTA.

I decided to experiment with this a little, so I entered the following code into the form-load method of a Windows Form application:

 private void Form1_Load(object sender,System.EventArgs e) {     IThreadView objThread;     Type objType;     objType=       Type.GetTypeFromProgID("ThreadDemo.FreeThreaded");     objThread=       (IThreadView)Activator.CreateInstance(objType);     lblAptState.Text=        Thread.CurrentThread.ApartmentState.ToString(); } 

The code here creates an instance of a COM class with the ProgID ThreadDemo.FreeThreaded by first calling GetTypeFromProgID on System.Type to fetch the Type object for the class, and then it calls CreateInstance on System.Activator to create an object. As you might have guessed already, the ThreadDemo.FreeThreaded COM class has a ThreadingModel of Free. The last line of the code fetches the ApartmentState of the current thread after instantiating the COM object and displays it in a label. The code here was designed to show you the Apartment that the current thread entered after you instantiated a COM object from managed code.

If you build the executable and then run it from Windows Explorer or a command prompt, or if you select Debug Start Without Debugging in Visual Studio .NET, the ApartmentState comes out to be MTA. You will also see MTA if you run the executable from within the Visual Studio .NET debugger by selecting the Debug Start. This is the expected behavior. If you do not specify otherwise , your thread will enter an MTA when it has to.

However, if I moved the exact same logic out of the form-load method and into the clicked handler for a button as follows:

 private void cmdSetApartmentState_Click(object sender, System.EventArgs e) {     IThreadView objThread;     Type objType;     objType=     Type.GetTypeFromProgID("ThreadDemo.FreeThreaded");     objThread=     (IThreadView)Activator.CreateInstance(objType);     lblAptState.Text=       Thread.CurrentThread.ApartmentState.ToString(); } 

The label shows STA if I run the executable directly and MTA if I ran it within the debugger.

Note

The ApartmentState always seemed to show MTA when I ran within the debugger indicating that there is code in the debugger that causes the thread to enter the MTA before it executes your code.


I came to a two-part conclusion based on these findings. First, a Windows Forms application enters an STA somewhere between the Form Load method and the call to the first event handler in the form. Second, if you want to be sure to get a particular Apartment type, you need to choose the Apartment type explicitly using the ApartmentState property on your thread. You also need to make this choice early in the life of the thread. If you don't make the choice early, the CLR may enter the wrong Apartment (that is, one that would require cross-apartment marshaling) before your initialization code. Unfortunately, this is easier said than done. You might think that you can just put a call to set the apartment state in the Main or the Form Load method of your application. However, this may not work because any code that uses COM in your application will initialize the ApartmentState, and, after it is set, it cannot change. The only surefire way to get the correct Apartment type is to set the ApartmentState before any startup code in your application is executed. Fortunately, there are two attributes, System.MTAThreadAttribute and System.STAThreadAttribute, that you may use for precisely this purpose. The following code shows you how to use System.STAThreadAttribute:

 [STAThread] static void Main() { // The apartment state is STA       Application.Run(new Form1()); } 

If you use this attribute on the Main method of my application, you are guaranteed that the ApartmentState will always be STA. If you want to enter an MTA, use the following code instead:

 [MTAThread] static void Main() { // The apartment state is STA       Application.Run(new Form1()); } 

When you initially create a Windows Forms project, Visual Studio .NET adds the System.STAThreadAttribute to the main function of your application as shown here:

 [STAThread] static void Main() {       Application.Run(new Form1()); } 

Keep this in mind. If you are using free-threaded COM objects in your application, you may want to change your managed client code to use [MTAThread].


Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net