When a COM object is instantiated on an STA thread, the thread usually has to implement a message pump in order to marshal calls to and fro other threads (see here).
One can either pump messages manually, or rely on the fact that some, but not all, thread-blocking operations will automatically pump COM-related messages while waiting. The documentation often doesn't help in deciding which is which (see this related question).
How can I determine if a thread-blocking operation will pump COM messages on an STA?
Partial lists so far:
Blocking operations which do pump*:
Thread.Join
WaitHandle.WaitOne
/WaitAny
/WaitAll
(WaitAll
cannot be called from an STA thread though)GC.WaitForPendingFinalizers
Monitor.Enter
(and thereforelock
) - under some conditionsReaderWriterLock
- BlockingCollection
Blocking operations which do not pump:
Thread.Sleep
Console.ReadKey
(read it somewhere)
*Note Noseratio's answer saying that even operations which do pump, do so for a very limited undisclosed set of COM-specific messages.
BlockingCollection
will indeed pump while blocking. I've learnt that while answering the following question, which has some interesting details about STA pumping:StaTaskScheduler and STA thread message pumping
However, it will pump a very limited undisclosed set of COM-specific messages, same as the other APIs you listed. It won't pump general purpose Win32 messages (a special case is
WM_TIMER
, which won't be dispatched either). This might be a problem for some STA COM objects which expect a full-featured message loop.If you like to experiment with this, create your own version of
SynchronizationContext
, overrideSynchronizationContext.Wait
, callSetWaitNotificationRequired
and install your custom synchronization context object on an STA thread. Then set a breakpoint insideWait
and see what APIs will make it get called.To what extent the standard pumping behavior of
WaitOne
is actually limited? Below is a typical example causing a deadlock on the UI thread. I use WinForms here, but the same concern applies to WPF:The message box will show the time lapse of ~ 4000 ms, although the task takes only 2000 ms to complete.
That happens because the
await
continuation callback is scheduled viaWindowsFormsSynchronizationContext.Post
, which usesControl.BeginInvoke
, which in turn usesPostMessage
, posting a regular Windows message registered withRegisterWindowMessage
. This message doesn't get pumped andhandle.WaitOne
times out.If we used
handle.WaitOne(Timeout.Infinite)
, we'd have a classic deadlock.Now let's implement a version of
WaitOne
with explicit pumping (and call itWaitOneAndPump
):And change the original code like this:
The time lapse now will be ~2000 ms, because the
await
continuation message gets pumped byApplication.DoEvents()
, the task completes and its handle is signaled.That said, I'd never recommend using something like
WaitOneAndPump
for production code (besides for very few specific cases). It's a source of various problems like UI re-entrancy. Those problems are the reason Microsoft has limited the standard pumping behavior to only certain COM-specific messages, vital for COM marshaling.