Threads

Summary

Apartments

COM+ needs a way to define associations between contexts and threads. It does this using apartments. From a COM+ perspective, an apartment is a collection of contexts that share the same degree of thread affinity. Calls (and callbacks) into a context are always serviced on a thread in that context's apartment. The following figure illustrates the relationship between objects, apartments, contexts, and processes.

Apartment types

COM+ defines three kinds of apartments,

This figure illustrates the relationships among a process, its apartments, and threads

Where do apartments come from?

Just like contexts, apartments cannot be created explicitly. Just as there is no CoCreateContext, there is no CoCreateApartment. Recall that where there is COINIT_APARTMENTTHREADED and COINIT_MULTITHREADED, there is no COINIT_NEUTRALTHREADED.

When a new instance of a COM+ class is created, the SCM may have to create not just a new context for it, but a new apartment. Each class defines its apartment type using the ThreadingModel registry entry. This entry determines the type of apartment that instances of that object will live in.

The ThreadingModel attribute takes precedence over all other COM+ declarative attributes

Main STA and Host STA

Definitions

Specifics

 

Component Created From … Class registered as threading mode
Apartment  Free  Neutral  Both  <absent>
SCM puts new object in a context in …
Main STA  Main STA  MTA TNA Creator’s apartment Main STA
Host STA Host STA
MTA, TNA, or remote apartment Host STA
STAn – created with _beginthread STAn  STAn

 

Threading Models

Each configured and raw-configured class's threading model is also specified in the COM+ catalog using the ThreadingModel catalog attribute. A class's threading model is specified din the registry for back-compatibility with COM. It is up to the programmer to keep the two values synchronized with each other. If there is a difference, error code CO_E_THREADING_MODEL_CHANGED results. 

The apartment type a new object ends in depends on two factors: its ThreadingModel attribute, and the apartment type of the thread calling CoCreateInstance[Ex]:

CoCreateInstance called from ThreadingModel
Apartment Free Neutral Both <absent>

STA

Current STA MTA TNA Creator's Apartment Main STA
MTA. TNA or remote apartment Host STA

The Host STA is created and managed by the COM+ pluming to host objects that require a high degree of thread affinity (an STA environment) but that created from an STA or MTA. The Main STA is the first STA created in the process. If the Host STA is the first STA created in the process, then the Host STA is also the Main STA. If the Main STA is created explicitly before the Host STA, then the Host STA is not the Main STA. Therefore, besides the Main and Host STAs, an STA will be created only if a new thread was created and initialized as STA. The SCM will put a new instance of class marked ThreadingModel=Apartment into an STA in a process's pool, if the class uses Activities

Default Contexts

Every apartment has a special context called the default context. A default context is different from all other non-default contexts in an apartment because it does not provide any run-time services. Code executing in the default context can call CoGetObjectContext to retrieve the object context, but it does not support any of the normal object context interfaces.

The default context is used in two instances:

  1. Threads do not specify environmental needs beyond their apartment types, so they execute in the default context their apartment provides.
  2. If an object in an apartment creates an instance of a non-configured class, and the new object cannot be added to its creator's context because of its threading model, the new object will be added to the default context context of an apartment of the appropriate type instead. This rule does not apply to a raw-configured class - If an object in an apartment creates an instance of a raw-configured class, and the new object cannot be added to its creator's context because of its threading model, the new object will be added to a brand-new non-default context in an apartment of the appropriate type instead. This is the only situation where non-configured and raw-configured classes behave differently during installation.

These rules are summarized in this table

Current Apartment Type and Class Threading Model Creating an instance of
Configured Class Non-Configured Class Raw-Configured Class
Compatible New context, same apartment Same context, same apartment Same context, same apartment
Incompatible New context, other apartment Default context, other apartment New context, other apartment

This is illustrated in this figure

Remote Object Creation

If a remote client creates an instance of a configured class deployed in a server application, the SCM has no choice but to put the object in a context and apartment in the server application:

The size of the STA pool is limited by the number of CPU. The upper and lower bounds of the pool are determined as follows:

7 + nCPU <= nSTA <= nCPU X 10        // 1 CPU: 8 to 10 STAs, 2 CPUs: 9 to 20 STAs

When a new STA-based object is created by an MTA, TNA or a remote client, the SCM puts the new object into a context in the process's Host STA. The STA pool does not get used in any of these cases. This is the SCM's default behavior. The SCM will put a new instance of class market ThreadingModel=Apartment into an STA in a process's pool, if the class uses Activities.

Cross-Apartment Calls

All cross-apartment calls require a thread-switch.

STA Complexities

Cross-Apartment Call Overhead

apartment Guidelines

Activities

Recall that the default behavior of the SCM is to put COMAdminThreadingModelApartment classes created by MTA, TNA or remote clients in the Host STA. This makes concurrent access to these object impossible. Why doesn't the SCM use a process's thread pool, and how can we change that?

There are two reasons for this behavior:

  1. It reflects classic COM, which did exactly the same things when STA-based objects where created from an MTA thread of a remote client.
  2. A threading model alone does not give the SCM enough information to allocate STA-based objects to STAs efficiently in all cases.

Therefore. the SCM creates new STA-based objects in the Host STA, unless it has additional information that explains how the new object it is creating relates to the other objects in the process. This is where activities come in.

Enter Activities

An activity is a group of contexts in one or more processes that were created to do work for a single client. If according to the object-per-client mode, clients don't chare objects, they don't share contexts, and therefore, contexts will never be accessed concurrently:

The following figure illustrates the relationship among processes, contexts, objects, and activities.

Where do Activities Com From?

Like contexts, and apartments, activities cannot be created explicitly: Activity creation and propagation is implicitly tied to context creation, which is implicitly tied to object creation. The Synchronization COM+ attribute determines how each instance of a configured class relates to activities (i.e., shares its creator's activity, its own new activity or no activity at all.)

The following table lists the five possible settings:

Attribute CSE Name Is In Activity? Shares Creator's Activity
COMAdminSynchronizationNone Not Supported Never Never
COMAdminSynchronizationSupported Supported If creator is in activity If creator is in activity
COMAdminSynchronizationRequired Required Always If creator is in activity
COMAdminSynchronizationRequiresNew Requires New Always Never
COMAdminSynchronizationIgnored

(only for raw-configured classes)

Disabled If creator is in activity and object shares creator's context If creator is in activity and object shares creator's context

 

Detecting Presence of an Activity

Every activity has a unique GUID, retrieved from IObjectContextInfo::GetActivityId method

Allocating STA Objects to Apartments

Recall that the SCM puts STA-based objects created by an MTA, TNA or remote clients into the server process's Host STA. The SCM does not use the process's pool of STA. However, if the SCM knows which STA-based objects in a process are used together, it can map them to a single apartment. Calls between these objects would be less expensive, and a single client request will use only a single STA thread. Activities tell the SCM what it needs to know to do this. The following table summarizes how activities change the way the SCM maps STA objects to apartments

CoCreateInstance called from a context in ... Class registered as ThreadingModel = Apartment, and Synchronization = ...
Not Supported Supported Required Requies New Disabled
The SCM puts a new object in a context in ...
An Activity and an STA No Activity, same STA Creator's Activity, Same STA New Activity, a Different STA Same Activity/STA, if other settings allow
An Activity and an MTA, TNA or remote apartment No Activity, Host STA Creator's Activity, Host STA Creator's Activity, that Activity's STA No Activity, Host STA
No Activity and an STA No Activity, Same STA New Activity, a Different STA No Activity, Same STA
No Activity and an MTA, TNA or remote apartment No Activity, Host STA No Activity, Host STA

Activities are propagated from one context to another as new objects are created.

Serializing Calls

Activities have two purposes:

  1. Allow the SCM to map STA-based objects to apartments based on the assumption that because all contexts in an activity belong to a single client, access to those contexts can be serialized.
  2. Based on the assumption that because the class code executing in a client's contexts was written with the object-per-client model in mind and is not thread-safe, access to those contexts should be serialized. 

When a call into a context in an activity is pre-processed, the interception plumbing acquires the activity's lock before dispatching the call. The lock is released when the call is post-processed. In effect, this serialized work in an activity. The causality entering the context has to acquire the lock before it can proceed.

In the following figure, if a call is in progress inside object A in context A, it must have acquired activity's A lock. If another client attempts to enter context C to invoke a method on object C, it has to wait, even though nothing is happening in context C, and there are plenty of MTA threads available. This is because work in an activity is serialized.


There is one exception to this rule. COM+ does not serialize access to an activity across process boundaries.

Activity Reentrancy

What happens when an object in an activity makes an outbound call to an object that is not in the activity? The activity lock cannot be released because the original call that acquired the object has not completed. If the lock is held, a callback from the second object back into any context in the activity must not cause a deadlock. Recall that activity may include contexts in any type of apartment. so work in an activity may be done on different physical threads. Activities are locked on a per-causality basis. When the interception plumbing acquires an activity lock, the lock is owned by the current causality, as identified by its CID. If a reentrant call is part of the same causality that owns an activity's lock, the call is processed.

In the figure below, causality A enters activity A to execute a method on object A in context A in an MTA. While the call is in progress, the causality makes an outbound call through a proxy and leaves the context, the activity, and the apartment. While that outbound call is in progress, the causality makes another nested call back into activity A. Because work is being done inside an MTA, it will be dispatched on a different thread. The MTA thread servicing the causality's original call is blocked inside the proxy and the channel while it waits for the causality's outbound call to complete. Because activity locking is based on CID rather than thread ID, the callback can use whatever thread it likes. If another causality attempts to enter activity A, it will block until the current causality leaves the activity permanently. This is different from reentrancy in STAs: an STA allows a second causality to enter an apartment if the causality that's using the apartment thread makes an out-of-apartment call.

Activity Deadllock

Activities work with causalities rather than with threads. There are many ways to create activity-based deadlocks. There are three cases where they can occur. They all occur in situations where multiple causalities are trying to enter the same activity:

  1. Having two causalities, each of which already owns an activity lock. A deadlock occurs if both call into each other's activities. This kind of deadlock may also occur with a single activity that spawns process boundaries because activity locking occurs on a per-process basis.
  2. Spawning a thread. Assume causality A owns the lock for activity A. Causality A is executing a method on an object that starts a new thread and then wait indefinitely for it to complete. Since a new thread executes in the default context, and since a new thread is assigned a new CID, it is not part of the causality that spawned it. If the new thread tries to call into activity A, the interception plumbing will stop it and make it wait for the activity's lock. But the thread is part of another causality and will not be allowed into activity A until causality A which is waiting indefinitely for the thread to complete, leaves the activity. Therefore, deadlock.
  3. Relying on the activity serialization mechanism towork with STA-based objects (see page 153 for full description)

Activity Guidelines

Synchronization Required is the preference in COM+. The other synchronization settings are useful only if you are planning on stepping outside the object-per-client model or starting your own threads.

STA-based objects use activities to gain access to a process's pool of STAs. MTA and TNA-based objects use activities to ensure seialized access to their data members. And all objects need activities if they want to use other COM+ services that rely on them. For all these reasons, Synchronization Required is the preference in COM+