Thursday, November 17, 2005

Using COM Objects in Multi-Threaded .NET Applications

This article discusses how .NET applications, including multi-threaded .NET applications, use the different kinds of COM objects. If you are writing a .NET application that uses COM objects, this article should help you avoid some problems related to COM objects and threading.

Background Information on .NET Threads
The following is a quote from the .NET Framework Class Library Help in Visual Studio 2003:

An apartment is a logical container within a process for objects sharing the same thread access requirements. All objects in the same apartment can receive calls from any thread in the apartment. The .NET Framework does not use apartments, and managed objects are responsible for using all shared resources in a thread-safe manner themselves.

Because COM classes use apartments, the common language runtime needs to create and initialize an apartment when calling a COM object in a COM interop situation. A managed thread can create and enter a single-threaded apartment (STA) that allows only one thread, or a multithreaded apartment (MTA) that contains one or more threads.

COM classes created with Visual Basic 6.0 have a ThreadingModel of Apartment which means they will be placed into a single-threaded apartment (STA) when they are created. COM classes created with Visual C++ 6.0 can have a ThreadingModel of Apartment, Free, or Both. COM classes with a ThreadingModel of Free will be placed into a multithreaded apartment (MTA) when they are created. COM classes with a ThreadingModel of Both will be placed into whatever apartment their creator is in. If their creator is not in an apartment, they will be placed into an MTA.

The Examples
All of the results shown below were created using a ComObjectThreading.exe application I wrote. This application will create the indicated number of instances of the selected COM object using one of three methods. If Object Location is set to Main Thread, the COM objects are created on the application’s main thread. If Object Location is set to Dedicated MTA Threads, the application creates the indicated number of MTA threads and each MTA thread creates a copy of the COM object. If Object Location is set to Dedicated STA Threads, the application creates the indicated number of STA threads and each STA thread creates a copy of the COM object.

There are four different COM classes you can create with this application:
  • CsgVbComInfo.ThreadInfo
  • CsgVcComInfo.ApartmentThreadInfo
  • CsgVcComInfo.BothThreadInfo
  • CsgVcComInfo.FreeThreadInfo
CsgVbComInfo.ThreadInfo (written in Visual Basic 6.0) and CsgVcComInfo.ApartmentThreadInfo (written in Visual C++ 6.0) are COM classes with a ThreadingModel of Apartment. CsgVcComInfo.BothThreadInfo is a Visual C++ 6.0 COM class with a ThreadingModel of Both. CsgVcComInfo.FreeThreadInfo is a Visual C++ 6.0 COM class with a ThreadingModel of Free.

The following examples demonstrate which thread a COM object is assigned to in different scenarios.

Example 1: Apartment Class - Main Thread
The following are the results of creating 5 instances of the CsgVbComInfo.ThreadInfo (Apartment) class with Object Location set to Main Thread:

GUI Thread ID: 3568
Object Location: Main Thread
Object Created: CsgVbComInfo.ThreadInfo

Instance   Application   
Thread ID
Object
Thread ID   
135683568
235683568
335683568
435683568
535683568

Since the main application thread in this application is an STA thread, the Apartment COM object can be executed on it. This is a good scenario because there are no thread context switches when going from the .NET application to the COM class.

Example 2: Free Class - Main Thread
The following are the results of creating 5 instances of the CsgVcComInfo.FreeThreadInfo (Free) class with Object Location set to Main Thread:

GUI Thread ID: 3568
Object Location: Main Thread
Object Created: CsgVcComInfo.FreeThreadInfo

Instance   Application   
Thread ID
Object
Thread ID   
135682848
235682848
335682848
435682848
535682848

Since the main application thread is an STA thread and COM objects with a ThreadingModel of Free must be executed on an MTA thread, the Free COM object has to be executed on a different thread. This is a less than ideal scenario because there is a thread context switch when going from the .NET application to the COM class. Depending on how often the COM object is used, this could impact performance.

Example 3: Both Class - Main Thread
The following are the results of creating 5 instances of the CsgVcComInfo.BothThreadInfo (Both) class with Object Location set to Main Thread:

GUI Thread ID: 3568
Object Location: Main Thread
Object Created: CsgVcComInfo.BothThreadInfo

Instance   Application   
Thread ID
Object
Thread ID   
135683568
235683568
335683568
435683568
535683568

Since COM objects with a ThreadingModel of Both can be executed on either an STA or an MTA thread, the COM object can be executed on the main thread. This is a good scenario because there are no thread context switches when going from the .NET application to the COM class.

Example 4: Apartment Class - Dedicated MTA Threads
The following are the results of creating 5 instances of the CsgVbComInfo.ThreadInfo (Apartment) class with Object Location set to Dedicated MTA Threads:

GUI Thread ID: 3568
Object Location: Dedicated MTA Threads
Object Created: CsgVbComInfo.ThreadInfo

Instance   Application   
Thread ID
Object
Thread ID   
438323692
114163692
34923692
29043692
523563692

Since the threads have their ApartmentState property set to MTA, the Apartment COM objects cannot be executed on them. The application creates and executes the Apartment COM objects on a special thread I am calling the Default STA thread. This is not the same thread as the main application thread (although the Apartment COM object could execute on that thread), but it is a dedicated thread that is created to handle Apartment COM objects that cannot be executed on their caller’s thread. There is only one Default STA thread for an application, so all requests for Apartment COM objects coming from MTA threads are processed by this one thread. If multiple MTA threads make requests of Apartment COM objects at the same time, all of those requests are queued and processed one at a time. Of all the scenarios presented in this article, this is the worst. Not only do you have a thread context switch when a COM object is used, but all of the COM objects reside on the same thread. Because of the thread context switching, this scenario is actually a little worse than having a single threaded application.

Example 5: Free Class - Dedicated MTA Threads
The following are the results of creating 5 instances of the CsgVcComInfo.FreeThreadInfo (Free) class with Object Location set to Dedicated MTA Threads:

GUI Thread ID: 3568
Object Location: Dedicated MTA Threads
Object Created: CsgVcComInfo.FreeThreadInfo

Instance   Application   
Thread ID
Object
Thread ID   
235203520
527242724
339763976
429962996
124962496

In this scenario each COM object is executed on the same thread as its caller. This is a very good scenario because there are no thread context switches when going from the .NET application to the COM class and multiple threads can execute at the same time.

Example 6: Both Class - Dedicated MTA Threads
The following are the results of creating 5 instances of the CsgVcComInfo.BothThreadInfo (Both) class with Object Location set to Dedicated MTA Threads:

GUI Thread ID: 3568
Object Location: Dedicated MTA Threads
Object Created: CsgVcComInfo.BothThreadInfo

Instance   Application   
Thread ID
Object
Thread ID   
125682568
232843284
322562256
519361936
427722772

Since a COM class with a ThreadingModel of Both can be executed on an MTA thread, the COM objects are executed on the same thread as their callers. This is a very good scenario because there are no thread context switches when going from the .NET application to the COM class and multiple threads can execute at the same time.

Example 7: Apartment Class - Dedicated STA Threads
The following are the results of creating 5 instances of the CsgVbComInfo.ThreadInfo (Apartment) class with Object Location set to Dedicated STA Threads:

GUI Thread ID: 3568
Object Location: Dedicated STA Threads
Object Created: CsgVbComInfo.ThreadInfo

Instance   Application   
Thread ID
Object
Thread ID   
240804080
1580580
420562056
310121012
520682068

In this scenario each COM object is executed on the same thread as its caller. This is a very good scenario because there are no thread context switches when going from the .NET application to the COM class and multiple threads can execute at the same time.

Example 8: Free Class - Dedicated STA Threads
The following are the results of creating 5 instances of the CsgVcComInfo.FreeThreadInfo (Free) class with Object Location set to Dedicated STA Threads:

GUI Thread ID: 3568
Object Location: Dedicated STA Threads
Object Created: CsgVcComInfo.FreeThreadInfo

Instance   Application   
Thread ID
Object
Thread ID   
331962608
537203780
12923780
233363780
421403780

Since each of the threads have their ApartmentState property set to STA, the Free COM objects cannot be executed on them. The application creates and executes the Free COM objects on an MTA thread. Since MTA COM objects can be executed on any MTA thread, when a caller accesses the COM object, it will be placed onto any available MTA thread. That is why there are two different Object Thread ID values in the results. As the number of dedicated STA threads is increased, there is more variation in the number of MTA threads used. In one scenario where 50 dedicated STA threads were created, 10 MTA threads were used, and in another scenario where 500 dedicated STA threads were created, 164 MTA threads were used. This is a less than ideal scenario because there is a thread context switch when going from the .NET application to the COM class. Depending on how often the COM object is used, this could impact performance.

Example 9: Both Class - Dedicated STA Threads
The following are the results of creating 5 instances of the CsgVcComInfo.FreeThreadInfo (Free) class with Object Location set to Dedicated STA Threads:

GUI Thread ID: 3568
Object Location: Dedicated STA Threads
Object Created: CsgVcComInfo.BothThreadInfo

Instance   Application   
Thread ID
Object
Thread ID   
280808080
180768076
480888088
380848084
580928092

Since a COM class with a ThreadingModel of Both can be executed on an STA thread, the COM objects are executed on the same thread as their callers. This is a very good scenario because there are no thread context switches when going from the .NET application to the COM class and multiple threads can execute at the same time.

Summary
The following table summaries the examples shown above. The left hand column indicates the ApartmentState of the caller’s thread. The top row indicates the ThreadingModel of the COM class being created. The contents of the table indicate which thread will process the COM object.

ApartmentState
of Thread
COM Class Threading Model
ApartmentFreeBoth
STASame STAMTASame STA
MTADefault STASame MTASame MTA

The ideal scenarios are the ones that are either Same STA or Same MTA. The worst scenario is the Default STA scenario.

Lessons Learned
  • Whenever possible, create your COM classes to have a ThreadingModel of Both. This will allow them to work optimally in both STA and MTA environments. Unfortunately this is not possible with Visual Basic 6.0.
  • Be sure to consider the ApartmentState of the thread and the ThreadingModel of the COM class whenever using COM classes in .NET applications.
  • Avoid creating COM classes with a ThreadingModel of Apartment from a thread with an ApartmentState of MTA. Be careful when using COM classes created in Visual Basic 6.0 because they will have a ThreadingModel of Apartment.

No comments: