|< BACK  NEXT >|
The extent of thread-safe code varies from application to application. At one extreme there are legacy applications that are not at all thread-safe. At the opposite end of the spectrum are well-crafted applications that make sophisticated use of the thread synchronization primitives. Then there are other applications that have special threading constraints, such as dependency on TLS or use of user-interface primitives.
An application should still be able to use components from another application without re-architecting its own synchronization strategy.
To facilitate transparent use of an object irrespective of its threading strategy, COM treats an object s concurrency constraints as yet another runtime requirement. The developer of the component decides on the threading strategy, or the threading model, as it is formally referred to.
Recall from Chapter 5 that any run-time requirement of an object becomes part of the object s context. It is possible that many contexts in a process share the same concurrency constraints. COM+ groups contexts of similar concurrency constraints into an abstraction called an apartment. The primary role of an apartment is to help COM+ determine which threads in the process are allowed to dispatch interface method calls in a particular context.
A process contains one or more apartments, and an apartment contains one or more contexts. This is illustrated in Figure 6.1.
Note that a context resides in exactly one apartment; no two apartments share the same context. If an object reference needs to be passed from one apartment to another, it is implicit that the reference has to be passed from one context to another. And we already know from Chapter 5 that such a reference has to be marshaled, either explicitly or via COM+ support.
Those of you who have been programming in classic COM know that a reference does not need marshaling when passed within the same apartment. This is different under COM+. There may be many contexts within the same apartment. If a reference is being passed from one context to another context, the reference has to be marshaled, even if the contexts belong to the same apartment.
A thread enters an apartment by calling a COM API, CoInitializeEx. The following is its prototype:
WINOLEAPI CoInitializeEx(/*IN*/ LPVOID pvReserved, /*IN*/ DWORD dwCoInit);
The first parameter for this API is reserved for future use. The second parameter indicates the type of apartment the thread would like to enter into.
Once a thread enters an apartment, it cannot enter another apartment without leaving the current apartment first.
To leave the current apartment, the thread can call another COM API, CoUninitialize. The following is its prototype:
For proper cleanup, CoUninitialize should be called once for each successful call to CoIntializeEx.
It is important to understand that a thread is not an apartment. A thread enters and leaves an apartment. Only objects (along with their context objects) reside in an apartment.
COM+ defines three types of apartments an object can reside in:
Single-threaded apartments (STA)
Multithreaded apartments (MTA)
Thread-neutral apartments (TNA)
Let s examine each of these in detail.
As the name implies, only one thread can execute in an STA. More precisely, there is only one specific thread associated with an STA and this is the only thread that can ever execute in the STA. As a result, all the objects that reside in the STA will never be accessed concurrently.
Because of this thread affinity, STAs are good for:
Objects that use TLS as intermediate storage between method calls.
Objects that do not have any protection (synchronization support) for their member data and therefore would like to avoid any shared data conflict.
Objects dealing with user interfaces.
When a thread calls CoInitializeEx passing the COINIT_APARTMENTTHREADED flag, COM+ creates a new STA and associates that STA with the thread. All objects that are subsequently created in the new STA will receive their calls only on this specific thread.
To enter an STA, COM provides a simplified variation of CoInitializeEx, CoInitialize, that developers can use. The following is its prototype:
WINOLEAPI CoInitialize(/*IN*/ LPVOID pvReserved);
Each thread within a process that calls CoInitialize (or CoInitializeEx with COINIT_APARTMENTTHREADED flag) gets a new STA. Therefore, a process may contain many STAs.
When the thread calls CoUnintialize, the STA gets torn down.
In an MTA, multiple threads can execute concurrently.
When a thread calls CoInitializeEx, passing COINIT_MULTITHREADED value as the apartment type, COM creates an MTA for the process, if one does not already exist. This thread, and any other thread that subsequently calls CoInitializeEx passing COINIT_MULTITHREADED as the apartment type, will use the same MTA. Therefore, a process can have at most one MTA.
Any object that is created in the MTA can receive its method calls from any MTA thread in the process. COM does not serialize method calls to the object. As method calls can be executed concurrently, the object should implement its own synchronization logic for guarding member and global data. In addition, as any MTA thread could invoke the object s method, no thread-specific state may be stored in the object (by using TLS, for example).
As the synchronization logic is applied at the implementation level, the developer gets to fine-tune the synchronization mechanism, resulting in a higher level of performance. Of course, the higher performance comes at the expense of more complexity in the code since the developer has to address the issues related to multithreaded programming.
When the last thread that is using the MTA leaves it (by calling CoUninitialize), the apartment gets torn down.
Recall from our earlier discussion that no two apartments share the same context. Therefore, a cross-apartment method call automatically implies a cross-context call. Combining this fact with our knowledge of cross-context marshaling from Chapter 5, we can state that if the caller s thread is in a different apartment than that of the object, the caller would get a proxy to the object. When the caller thread makes a method call to the proxy, COM+ would intercept and dispatch the call to an appropriate thread in the object s apartment. We will cover this architecture in detail in the next section. The important point to note here is that if an object resides in an STA or an MTA, a cross-apartment method call on the object requires a thread switch. And we know from our earlier discussion that a thread switch is a very expensive operation.
This performance problem can be solved if a thread-neutral apartment is used instead of an STA or an MTA. Under TNA, the client always receives a lightweight proxy. When a method call is made on this proxy, COM+ switches to the object s context without causing a thread switch.
Unlike STA and MTA, a thread cannot enter or leave a TNA. Only objects can reside in a TNA.
Also note that there can be only one TNA in a process (similar to MTA).
The TNA is the preferred apartment for objects that do not have any thread affinity. As we will see in a later section, under TNA, the developer can still choose between providing its own fine-grain synchronization logic or letting COM+ serialize the method calls to an object.
You may be thinking that TNA seems similar to the FTM that we covered in the previous chapter. After all, both of them execute incoming calls in the caller s thread. The difference is that a TNA-based object has its own apartment (and context). An FTM-based object does not have a context of its own; it uses the caller s context. The FTM is used if an object has reasons to use the caller s context. The TNA is preferred for all other cases.
|< BACK  NEXT >|