Threads
Summary
- COM+ supports STA, MTA, and TNA. TNA is rarely
the best apartment type for a COM+ object to live in.
- The ThreadingModel
attribute takes precedence over all other COM+ declarative attributes.
- Every apartment has a special context called
the default context. Threads do not specify environmental needs
beyond their apartment types, so they execute in the default context their apartment
provides.
- Threading model Both
is preferred in COM+ because they always activate as close to their creator
as possible.
- 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 interception plumbing serializes calls
into activities.
- 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.
- Synchronization
Required is the preference in
COM+.
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.
COM+ defines three kinds of apartments,
- STA
- Always contains only one thread.
- COM+ interception plumbing serializes concurrent calls into contexts
in an STA using its thread's Windows message queue.
- MTA
- Can contain many threads.
- COM+ interception plumbing does not serialize concurrent calls into
contexts in an MTA.
- TNA (Thread-neutral apartments)
- TNAs have no thread affinity.
- A TNA does not contain threads of its own.
- All calls into contexts in a TNA are serviced on the caller's thread.
- MTA and STA threads execute work in their respective apartments, but
they can temporarily visit a TNA to call methods on objects that live
there. After each method call on an object that lives in a TNA, the
visiting thread leaves the TNA and returns to its home apartment
- Calls into TNA require interception but no thread switch.
- An MTA or STA that is visiting a TNA may call back into contexts in
its home apartment. Again this requires interception but no thread
switch.
- The interception plumbing does not serialize calls into
contexts in a TNA. Therefore, because work in a TNA can execute
concurrently, there is only one TNA in a process.
- TNA is rarely the best apartment type for a COM+ object to live in.
This figure illustrates the relationships among a process, its apartments,
and threads
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
- If a new object has a threading model compatible with its creator, its
ability to live in its creator's context depends on the other services that
it needs.
- If a new object has a threading model incompatible with its creator, it
cannot possibly live in its creator's context even if it provides all the
run-time services the new object needs. The SCM puts the new object in a new
apartment. This is the other way (beside CoInitializeEx)
that a new apartment may be created.
Main STA and Host STA
Definitions
- Main STA is defined as the first STA to be created in a process using CoInitialize(NULL)
or CoInitializeEx( COINIT_APARTMENTTHREADED).
- Host STA is defined a as the first STA to be created by COM. For example,
if an MTA client creates an Apartment-threaded component, the component will
be loaded into the host STA).
Specifics
- It is possible to have a Main STA and Host STA side by side (Main STA
created before Host STA):
- A client application initializes COM libraries with CoInitialize(NULL).
Hence the Main STA is generated.
- The client creates an Free-Threaded component, hence an MTA is
generated
- The MTA component creates an Apartment-threaded component, hence a
Host STA is generated to host the Apartment-threaded component.
Note: The client runs in the Main STA. If the client creates
Apt-threaded components, they will be in the creator’s apt, i.e., Main
STA.
- It is possible to have the Main STA be the same as the Host STA (Host STA
is the first STA to be created by COM):
- A client application initializes COM libraries with CoInitializeEx(COINIT_FREETHREADED).
Hence, an MTA is generated
- The client or the component (both in MTA) create an Apartment-threaded
component. Hence the Host STA is generated. AND the Host STA also
becomes the Main STA.
- The above can be summarized as follows:
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
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:
- Threads do not specify environmental needs beyond their apartment types,
so they execute in the default context their apartment provides.
- 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:
- COMAdminThreadingModelFree and COMAdminThreadingModelboth
classes: Created in a server process's MTA.
- COMAdminThreadingModelNeutral classes:
Created in a server process's TNA
- COMAdminThreadingModelApartment classes:
Created in one of server process's STA (the Host STA, or if Activities are
used an STA from the STA pool)
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
- RPC calls into an STA are queued as messages on the Windows message queue
since an STA contains one and only one thread. Each caller gets blocked
until its message gets pumped and its call serviced. This means the STA
thread must continually pump threads, and must never block indefinitely. If
the STA thread blocked indefinitely, calls and callbacks would also have to
wait (for example, if the STA was waiting indefinitely on a mutex, and the
thread that owned the mutex tired to call the STA, both threads would grind
to a halt.)
- COM method calls are synchronous, blocking operations. When a COM object
in an STA makes an outbound call (i.e., crosses apartment boundary) through
a proxy, the thread seems to block while waiting for a response.. But the
thread is not blocked, it is actually inside the channel in a message loop,
while another thread makes the genuinely blocking RPC call. If a call
happens to arrive while the STA thread is in the channel, it will be queued
in the STA's queue and then serviced by the STA thread - the STA thread is
still in the channel pumping messaged. All this is necessary to support
callbacks and to make sure Windows messages are processed.
- Calls and callbacks into contexts in a process's TNA are serviced directly
by the calling thread. If an STA thread calls into a TNA in the same
process, no thread switch is required, and none of the issues described
above arise. If an STA thread that is visiting a TNA calls back into its
home apartment, o thread switch is required, and none of the issues
described above arise. However, if an STA thread that is visiting a TNA
calls out into some other apartments, a thread switch is necessary and all
the above issues arise.
Cross-Apartment Call Overhead
- In general, cross-apartment calls are at least one order of magnitude
slower than cross-context calls. Call stacks have to be marshaled in cross-apartment
and cross-context calls, but a thread switch is required in cross-apartment
calls.
- If a thread in an STA or MTA calls a method of an object in the same
process's TNA, marshaling occurs (cross-apartment call) but no thread-switch
happens. Despite this fact, the performance of calls into contexts in a TNA
are noticeably slower that the performance of cross-context calls in the
same apartment.
- Calls into the default context in a TNA take roughly twice as long as
cross-context calls in the same apartment.
- Calls into non-default contexts in a TNA take roughly four-times as long
as cross-context calls in the same apartment.
- Threading model Both is
preferred in COM+ because they always activate as close to their creator as
possible. Although interception may be required to cross a context
boundary, no thread switch is required as long as object and its creator are
in the same process. If the object and its creator are in different
processes, the object will be placed in the server's MTA. Remember that the
MTA is dangerous only you do not follow the object-per-client model. If some
clients do not use the object-per-client model, you can rely on activation
to protect the object from concurrent access. In general, threading
model Both is the way to go.
- While threading model Both is preferred, there may be situations
when another threading mode is appropriate:
- VB6 only supports threading model Apartment.
- If a class uses instances of other classes, it might want to share
their threading model. For example, ADO and MSXML classes were designed
for use from VB6 and use threading model Apartment. Instances of
these classes are expensive to call from an MTA. A class using ADO or
MSXML might just as well use their threading mode, Apartment.
- If a class has a method that blocks a thread indefinitely without pumping
messages, the class must use threading model Free. Never block
an STA thread indefinitely without pumping messages
WaitForSingleObject( hSomeEvent, 0
); // OK.
Returns immediately
WaitForSingleObject( hSomeEvent,
1000 ); // OK.
Returns after 1 sec. Don't wait for long.
WaitForSingleObject( hSomeEvent, INFINITE
); // Never
In the last case, if the thread that is responsible for signalling
the event is waiting to access the STA thread, it will lead to a deadlock.
Windows 2000 introduces a new COM-aware synchronization API: CoWaitForMultipleHandles.
- If an object is called from more than one apartment in a process, it
should use threading model Neutral. All calls to the object
will be intercepted but none of them requires a thread-switch. As a
corollary, if an object is called from exactly one apartment in a process,
it should not use threading model Neutral because calls into a TNA are
always more expensive than cross-context calls in the same apartment.
- A class should never use threading model Main.
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:
- It reflects classic COM, which did exactly the same things when STA-based
objects where created from an MTA thread of a remote client.
- 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:
- Every context created in a COM+ process is associated with at most one
activity.
- Activities are a part of context and can flow to new contexts as they are
created.
- When a new context is created, it can be added to its creator's activity,
its own new activity, or no activity at all.
- A process can contain multiple activities for multiple clients.
- Unlike apartments, activities can extend beyond processes.
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
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.
Activities have two purposes:
- 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.
- 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.
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:
- 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.
- 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.
- Relying on the activity serialization mechanism towork with STA-based
objects (see page 153 for full description)
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+