Sasha Goldshtein is a Senior Consultant for Sela Group, an Israeli company specializing in training, consulting and outsourcing to local and international customers.Sasha's work is divided across these three primary disciplines. He consults for clients on architecture, development, debugging and performance issues; he actively develops code using the latest bits of technology from Microsoft; and he conducts training classes on a variety of topics, from Windows Internals to .NET Performance. You can read more about Sasha's work and his latest ventures at his blog: http://blogs.microsoft.co.il/blogs/sasha. Sasha writes from Herzliya, Israel. Sasha is a DZone MVB and is not an employee of DZone and has posted 202 posts at DZone. You can read more from them at their website. View Full User Profile

Managed Synchronization Primitives and Thread Apartment States

08.27.2008
| 4683 views |
  • submit to reddit

The managed synchronization mechanisms, including Monitor, WaitHandle.WaitAny, ManualResetEvent, ReaderWriterLock, Thread.Join, GC.WaitForPendingFinalizers and the rest of the family are not just a thin platform adaptation layer on top of the Win32 API.

The CLR needs to know exactly which threads are currently waiting for a synchronization mechanisms for a variety of reasons.  To mention two of them:

  • In hosting scenarios, the CLR host might want to limit the ability of application threads to perform synchronization at all (to ensure reliability and control over threads).
  • A waiting thread (in the WaitSleepJoin thread state) won’t be rudely aborted (by Thread.Abort) until it leaves the waiting state.

If all synchronization calls were straight P/Invoke-s to the Win32 services, these restrictions (among others) would be impossible to achieve.

An additional feature enabled by (most) of the managed synchronization mechanisms is message pumping.  When you call one of the above APIs in an STA thread, the CLR takes care of message pumping (as of Windows 2000, simply by calling CoWaitForMultipleHandles which calls MsgWaitForMultipleObjects) to ensure that STA COM objects within that STA thread can process incoming calls, which are delivered through the Windows message loop.

If this were not the case, interesting deadlocks could ensue.  For example, the finalizer thread might want to release an STA COM object, which requires marshaling the call to the STA thread.  If the STA thread has just called GC.WaitForPendingFinalizers and does not pump messages, a nasty deadlock occurs.  (There are numerous other examples of how this could be problematic, but I’ll omit them for brevity.  Courageous readers are welcome to read Chris Brumme’s post on apartments and pumping.)

However, the CLR is also smart enough to realize that on an MTA thread, there’s no need to pump messages – so it doesn’t.  A wait on an MTA thread is a true WaitForMultipleObjects, (almost) no strings attached.

For future reference, here’s what a call stack for an STA thread Thread.Join call looks like (.NET 3.5 x64, edited for brevity):

0:006> k
ntdll!NtWaitForMultipleObjects+0xa
KERNEL32!WaitForMultipleObjectsEx+0x10b
USER32!RealMsgWaitForMultipleObjectsEx+0x129
USER32!MsgWaitForMultipleObjectsEx+0x46
ole32!CCliModalLoop::BlockFn+0xbb
ole32!CoWaitForMultipleHandles+0x145
mscorwks!NT5WaitRoutine+0x77
mscorwks!MsgWaitHelper+0xed
mscorwks!Thread::DoAppropriateAptStateWait+0x67
mscorwks!Thread::DoAppropriateWaitWorker+0x195
mscorwks!Thread::DoAppropriateWait+0x5c
mscorwks!Thread::JoinEx+0xa5
mscorwks!ThreadNative::DoJoin+0xda
mscorwks!ThreadNative::Join+0xfa
0x642`80150b01

And here’s what a call stack for an MTA thread Thread.Join call looks like:

0:005> k
ntdll!NtWaitForMultipleObjects+0xa
KERNEL32!WaitForMultipleObjectsEx+0x10b
mscorwks!WaitForMultipleObjectsEx_SO_TOLERANT+0xc1
mscorwks!Thread::DoAppropriateAptStateWait+0x41
mscorwks!Thread::DoAppropriateWaitWorker+0x195
mscorwks!Thread::DoAppropriateWait+0x5c
mscorwks!Thread::JoinEx+0xa5
mscorwks!ThreadNative::DoJoin+0xda
mscorwks!ThreadNative::Join+0xfa
0x642`80150a86

Don’t you want to hug these MTA threads?  They are SO_TOLERANT.

Oh, and one more thing to wind this up.  You may have read that WaitHandle.WaitAll is not supported (at all) on an STA thread, and throws an exception if you attempt to call it.  Why does this make sense?

(… Dramatic suspense …)

Well, if you call MsgWaitForMultipleObjects from an STA thread and use bWaitAll=TRUE, you’re essentially saying that you want to wait for all the handles to become signaled and for a message to arrive.  This is clearly not the intent, and there’s hardly anything the CLR can do about it, so it forbids the whole situation.

There are workarounds (some described in Chris Brumme’s post which I cited above, some elsewhere), but they don’t fully address the situation.  For example, one alternative (which I recently used in a project) is to spawn a new MTA thread, have it perform the WaitHandle.WaitAll, and use Thread.Join to wait for that thread to complete.  However, if one of the handles has ownership semantics (e.g. a mutex), this breaks because the mutex will be owned by the wrong (and terminated – thus considered abandoned) thread.

However, one thing to keep in mind is this: The CLR does a whole lot of work to ensure managed synchronization works properly (and employs ruthless dark magic when necessary).  The worst thing you can possibly do is calling into Win32 synchronization primitives directly, bypassing the CLR.

References
Published at DZone with permission of Sasha Goldshtein, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)