To understand this page you need basic understanding of the
threads and some basic knowledge about COM (Component Object
Model). If you have some skills in COM programming in C++ it will
only help but is not a requirement. What are the threads?
Most modern Operating Systems (OS) support this feature - a
parallel execution of part of the program in the same memory
space. In other words the program is able to do its work in
several parallel logical threads. The OS is responsible to hide
the actual hardware abilities (number of processors etc.). Apparently
there is a problem - the synchronization. All the
threads use the same memory and even if they are planned to use
different variables at some point they must cooperate by passing
data to each other. As they are parallel any data accessed by
more than one of them is in danger - the thread can be
interrupted by the OS at any point and there is no guarantee the
data will be consistent when two or more threads write at the
same place in memory. For the purpose the OS supplies
synchronization objects and the threads can synchronize between
each other and access the critical data sequentially.
The COM objects and the threads
COM objects are code/data integration that follows a binary
standard which allows these objects to communicate to each
other. But they are created by applications - which in turn have
one or more threads. So certain COM object is always created in
certain thread and when it calls another object the other object
can be in the same or in another thread (or in another process
which is out of our interest here). So the thread
synchronization problems are COM synchronization problems as
well. As the COM objects can be created by the application in
various places (in different threads and under different circumstances) there is
no simple solution for the synchronization problems. As there
are other considerations related to another OS features the
problem becomes even harder. Still there are simple solutions
built-in - supplied by the COM libraries of the OS. To handle
the different situations COM separates the objects in two major categories - Apartment
threaded and Free threaded. Here comes
the term COM apartment. COM Apartment may contain one or
many threads and there are two apartment types Single
threaded and Multi-threaded. The apartments are
created by the application that uses the COM objects. The system
COM library is initialized in each thread created by the
application. Initializing it as single-threaded or
multi-threaded tells the COM how to deal with the objects
created further by the thread. All the threads with
multi-threaded initialization form together one and only
multi-threaded COM apartment, while each thread with
single-threaded COM initialized form a separate single threaded COM apartment.
Further if a thread creates new COM object it will be created by
default in the apartment the thread belongs to and all the calls
between objects in the same apartment are direct - i.e. COM does
nothing to synchronize or translate them. On contrary if the
objects that call each other are in separate apartments (two
single-threaded or one multi-threaded and one single-threaded)
COM handles the calls through internally created pair of
proxy-stub objects which are responsible for the
synchronization. How the synchronization works? COM
proxy-stubs take the full responsibility for the synchronization
for the objects in the single-threaded apartments. To do so the
system COM library uses a message loop in each of the single-threaded apartments
(remember they contain only one thread each). And all the calls
to or from that apartment are serialized - so only one call can
be in progress. Furthermore the call is always processed in the
same thread - the thread of the single-threaded apartment where
the object resides. So internally the call is just queued and
the apartment (the thread) picks it from the queue and executes
it. Therefore no matter how many threads with single-threaded
COM apartments you have your application will most likely work
as like it has only a single thread if there is a chain of
objects calling each other in the separate threads. E.g. if an
object in the first apartment calls an object from the second
apartment the first will wait the call to complete before any
other call from any apartment can proceed and so on - the entire
chain of calls must complete before any other call would be
allowed. Thus the price for this "automatic"
synchronization is the reduction of the actual number of the
concurrent logical threads in the application. To avoid this
issue the applications that need to support parallel processing
and wide variety of components in the same time most often rely
on free-threaded COM objects. The free threaded COM objects are
characterized by the fact that they are designed to process
calls from different threads without help from the COM library.
So, they take the responsibility for the synchronization on
themselves. Usually this is done with the so called critical
sections which allow the object to lock/unlock the access to
certain member variables. Unfortunately this technique is based
on the execution flow, so the actual effect is that the
execution of certain methods (or part of them) will wait until
another one finishes its "critical section" - i.e.
unlocks the code flow. This technique works perfectly if
considered for outside calls only - in other words object
processes calls from other objects/parts of the application.
However there is set back if a callback technique must be
implemented - a scenario in which the object calls member of
another object and then this second object needs to call back
one or more methods on the sourcing object during the process of
the initial call. Apparently it is very important to place the
lock/unlock points in the COM objects involved very carefully in
order to avoid dead locks. This is quite difficult and sometimes
even impossible. The above issue means that very often
free-threaded objects not designed to work in scenarios that
include call back techniques may cause troubles if forced to
participate in such applications.
Using threads with COM objects
Now we can try to illustrate the above. You will see that the
ActiveX Pack1 library contains two thread classes COMThread
and COMScriptThread. The
first class controls a thread and when it is started it
initializes COM in it. Then the application uses this thread
through the COMThread object. So, suppose we have created the
object and activated (Activate
method) it, after that we have the Execute
method which actually posts a request for a call. As seen from
its parameters you specify an object and a method to be called
on it.
What happens then? The COMThread object posts a
message in the message loop of the thread it manages. The thread
retrieves it and performs the call. Therefore the call occurs in
the thread and not in the thread of the application that created
the COMThread object. Up to this point everything is simple but
lets consider the object on which the specified method is
called. The most important question is "where is this
object". There are several possibilities:
It can be in the application's COM apartment - i.e. in the
same apartment where is the code that has created the
COMThread object. Depending on the threading model we may
have:
Application threading model |
Object's threading model (as registered) |
Location of the object |
Single threaded |
apartment or both |
The same apartment as the application |
Single threaded |
Free threaded |
In a separate free threaded apartment |
Free threaded |
both or free threaded |
In the same multi threaded apartment as
the application. |
Free threaded |
apartment |
In a separate (new) single threaded
apartment |
By application we mean not the entire application but only the
code that creates the thread.
On the other hand we are able to instruct the COMThread
object to initialize the thread it manages as multi-threaded or
as single threaded COM.
Now our application's code (which created the thread) is
running in a COM apartment. If it is a single threaded
apartment then this apartment is not able to process any
external calls because our code executes in it and any call
placed for it will wait until our code finishes. This means that
if we create an apartment threaded object and want to call
asynchronously method on it nothing good will happen. The object
we create will be in our (single threaded) apartment and the
COMThread object will actually post an external call to an
object in the apartment of our application. Thus the call will
have a chance to proceed only after we finish everything in our
thread. In most cases this is of no use as no parallel action
will occur, but using thread means that we want to achieve
something like that. It can be even worse our application may
exit before the thread call has chance to proceed.
If the situation is the same as above but we create a free
threaded object everything will be ok as the call will not be
sequenced through a message pump of its apartment thread. The
call will actually occur in the thread managed by the COMThread
object.
If the application runs in free threaded apartment the
situation will be different. The apartment threaded objects will
be created in separate single threaded apartments and a call
through the COMThread's thread will occur successfully because
it involves another apartment that has nothing in common with
our application's apartment.
If the object created is free/both threaded it will be in our
application's apartment. but it is a free threaded apartment and
no sequencing will be performed and again the call will be
executed asynchronously.
What else we must be concerned about? If you use these
techniques in ASP application (for example) then be aware that
different ASP implementations may run your pages in different
ways and you should check the documentation to see what kind of
COM apartments are used. Also note that the script languages are
able to run in multithreaded apartments but they perform on
their own some of the operations typical for the single threaded
COM apartments. So, the effect could be quite confusing and
undeterminable. To avoid the confusion in more universal way you
have another opportunity to ensure that the object on which
asynchronous calls will be performed (calls from separate
thread) lives in apartment you control. So, by using the COMApartment
object you can create a single threaded apartment or get access
to the free threaded apartment in the running process (or
implicitly create such if none exist at this moment). Then you
will be sure about everything you create there (through the
methods of the Pack1Creator
object accessible through the COMApartment's Creator
property). For example if you create a single threaded apartment
any apartment/both threaded object created in it will be in this
apartment and also every object directly created (using the
special syntax allowed in the Pack1Creator.CreateObject
you can create objects directly from the ActiveX pack1 DLL or
from another DLL or composite definition bypassing the COM
system). Then you can safely call it from the thread of your
COMThread object as you will know that the call will involve
only apartments outside your own.
OS notes
With the techniques mentioned above you can achieve almost
everything on the desktop Windows versions (Windows
95/98/ME/NT/XP/2k/2k3 and later), but on Windows CE based
machines you should know that there is no support for single
threaded apartments provided by the COM library. This means that
everything will run in free threaded apartments.
Now remember that the script languages behave a bit strange -
in general they are not able to process correctly calls to them
from thread other than the thread in which they were originally
created. So, a call through COMThread object will be impossible
- will cause fatal error. The desktop versions of the script
engines are more advanced but you should not rely on this even
if it seems that everything works correctly (it is most likely
just a coincedence!). On desktop Windows OS you can guarantee
that any call to your script will occur in the thread where it
is created by creating a host in a single threaded COMApartment
(see ScriptManager2).
Then each call from the COMThread's thread will be routed to the
message loop of the COMApartment's single threaded apartment and
the call will occur in its thread. But if you try to do this
with free threaded apartment it will mean that the call from the
COMThread's thread will be foreign for the script (from another
thread) and the operation will fail. As mentioned on Windows CE
(including Pocket PC, Smartphone etc.) you cannot run/call a
script in separate thread using this technique.
A solution for the scripts is COMScriptThread
which makes its own routing of the calls to the script and
guarantees that the script is created and used in only one
thread. This object saves all the efforts to prepare suitable
environment for the script (see the need of COMApartment and
COMthread object above) and also will run perfectly if
initialized free threaded. Which means that it also works
correctly on Windows CE. You will see that this class (COMScriptThread)
can also be instructed to initialize the thread it creates and
manages as single or multi threaded COM. But the object
sequences the calls through it on its own so the actual
initialization will not be vital. It will have impact only on
the performance - i.e. depending on what kind of other COM
objects are created for internal usage by the script in the
thread you can find that one of the both threading models will
lead to better results (the model that fits the most of the
objects created - e.g. if more of them live in the same
apartment the performance will be better).
Conclusion
(You can also read the other overview of the COM
and threads which is oriented more practically to the usage
of the ActiveX Pack1 objects.)
The developers who don't want or don't know enough about COM
can just use the COMScriptThread object to run scripts in
separate thread(s) asynchronously. This requires no advanced COM
knowledge not it requires you to know details about each object
used by your script running in this separate thread. If you have
stronger COM understanding and enough information about the
objects you use (suppose you want to call not scripts, but some
other code in separate thread - for instance you may have your
own modules written in VB or C++) you can use COMThread (and
COMApartment if needed) to construct whatever COM environment
they need.
When using COMApartment object note that you must keep it
alive until all the objects in it are meant to be alive. Failing
to do so will destroy them together with the COMApartment! For
example in ASP applications you should keep the COMApartment in
the Application or Session until the thread or threads that use
them are alive. Even if you use COMApartment without COMThread
(just to make sure that certain object is created in COM
apartment model of your choice) you must keep the apartment's
object. The best practice is to work with well managed/well
known number of COMApartment objects and keep them until
the application exits (in ASP case this means that once created
you save them in the Application and never set them to Nothing).
|