Introduction
Here are two things that I think developers should be aware of when programming
with STA threading model. I'm not trying to cover all pitfalls that one
might encounter, but only specific two that I thought can bite
developers..
Project: "Create an ActiveX component for some or other purpose."
At first I started with a simple ActiveX component using
"Apartment" threading model. Then added another COM class, which will
be passed to one of the events as argument. Created an events thread to
fire the events to client. All seemed to work fine. I wrote
various simple apps that tested the functionality in ATL/WTL, MFC, VB, and
VBScript/JScript all using same threading model.
STA Deadlock
Then I've tried MTA for
the container. Suddenly, it started to deadlock randomly. Why?
I thought if it works for STA what difference does it make if the container is STA
or MTA? COM should intervene and take care of apartment incompatibilities. It took
some quick research to find the
problem. Thanks to Valery Pryamikov's post on ATL list,
"Designing
Solutions with COM+ Technologies" (pp. 96-97) and "Transactional COM+" (pp.
130-131) things started to make sense. I've made a stripped down version
that shows the problem and you can download the STA Deadlock sample project.
Please run it in the debugger.
The deadlock will happen when you quit the app. Notice that
if you change the client's threading model to "Apartment" or in-proc COM server
to "Both" everything works. The problem comes
from the fact that client makes an outgoing call back on the
COM component passed in the event's function. Now,
if at the same time client calls back into main component for
whatever reason and blocks with message loop (i.e. to shutdown events thread in the
component while Unadvise
is called), there will be a deadlock.
When the client called back on the ISimpleObj
methods, "channel" is
already taken by the Unadvise
where I tried to shutdown the events
thread. So, both threads will be waiting for each other to get a hold of the
"channel". It's just one scenario where the deadlock could happen and can easily
be accomplished other ways. The nasty thing about this is that it may not happen
all the time and may not happen at all. It all depends on the right
circumstances. So be aware.
STA Reentrancy
This is widely known, but I just wanted to show it anyway because I never saw
concrete examples when I was starting with COM. Maybe someone will
find it helpful. So, after dealing with deadlock I got bitten by the STA
reentrancy. The component was using windows sockets to communicate with
the server. Now, that I've marked my component as "Both" and
synchronized all access to the component I thought I'm safe...But...One of
the methods is taking an interface pointer that is part of some
object. Now, inside the method I call methods on the interface
and send data out over the socket. Unfortunately, when I make the outgoing
call on another component, "STA reentrancy model" will yield the thread to
another incoming call if there is any. It can happen if client decides to
call my method from the thread different from the object's creating
thread. I can't assume that clients will do the right thing. So,
this was unacceptable because the actual server that my component communicates
with over sockets, expects a specific flow of information. I've
made a simple project that shows it in action using file instead of sockets. Look at STA Reentrancy if you run it, you'll see that the output to log
file is mixed up instead of having a sequential flow. Notice that if you change
the client's threading model to MTA it will work fine. COM will come in
and take care of synchronization for us.
After dealing with "Apartment", "Both", and
"Free" I would take MTA anytime over STA model - if possible of
course. I hope someone finds this article helpful.