How to pump message for COM STA threads in C#?
Asked Answered
D

2

8

I have a main STA thread that calls a lot methods on the COM object and a secondary STA thread that does a lot work on the same object too. I want the main thread and the secondary thread to work in parallel (i.e. I expect interlaced output from the main and the secondary). I know I need to pump messages in the main thread every now and then - calling Get/Translate/DispatchMessage in C++ will do the trick.

But I'm having problem getting the same strategy working in C#. At first I used CurrentThread.Join() in the main thread to give control to the second thread. It didn't work. Then I turned to Application.DoEvents() - I called it in the main thread whenever I wanted the second thread to run. The result is the second thread quickly grabs the control and won't let go - the main thread cannot continue until the second thread is all done.

I read documents that say Application.DoEvents() will process ALL waiting events - while GetMessage() retrieves only one message.

What's the correct thing to do? Is there C# equivalent of Get/Translate/DispatchMessage?

Thanks

UPDATE: The second thread is running too fast, sending a lot COM call messages to the main STA thread. I just added delays in the second thread to slow it down. Now two threads are basically running in parallel. But I still would like to know if there is C# equivalent of GetMessage/TranslateMessage/DispatchMessage.

Departmentalize answered 21/7, 2011 at 4:55 Comment(0)
V
9

Your original C++ code violated the STA contract. Which states that an interface pointer must be marshaled from one thread to another so that all calls on the object are made from only one thread. This is a hard requirement for single-threaded COM servers, not doing so risks the typical misery associated with make calls from multiple threads on code that is not threadsafe. Using two STA threads does not relieve you from this requirement, the object is owned only by the thread that created it. The 2nd thread is just another thread and calls from it cannot be made safely since the server doesn't support multi-threading.

You somehow got away with it in the C++ code, hard to imagine there wasn't a glitch now and then. COM cannot otherwise enforce the STA contract on in-process COM servers, only on out-of-process servers. Violating the contract generates RPC_E_WRONG_THREAD in that case.

Anyhoo, you are no longer getting away with this in a C# program. The CLR automatically marshals the interface pointer for you. The calls you make on the 2nd thread will be marshaled to the STA thread that owns the object. There is still interleaving but the 2nd thread's calls can only be delivered when the first thread goes idle and re-enters the message loop. There is no workaround for this, the CLR handling of the interface pointer is strictly by the rules.

This will have plenty of consequences for your code I imagine, the largest one is that the 2nd thread really doesn't accomplish much of anything anymore. There is no concurrency, all calls to the object are strictly serialized. And threadsafe. You'll probably be better off just making all the calls from one thread so that you won't have to dance around the considerable risk for deadlock. Making proper message pumping much less critical as a bonus. If the 2nd thread does other critical work then taking advantage of COM support for single-threaded code can be helpful.

Vyse answered 21/7, 2011 at 5:36 Comment(5)
Hi Hans, I'm not just getting away with it - I'm using global interface table int the C++ code to do marshalling. For C#, CLR is doing the marshalling internally for me. Also I understand there is no real concurrency in the STA model as all calls are serialized in the apartment. Anyway now it seems two threads are running in parallel, that's what I wanted. But now I do doubt what is the point in STA multithreading - does it achieve anything at all?Departmentalize
Hmm, the 'interleaving' put me on the wrong track. Well, no reason then for the C# code to behave any different. Don't use Thread.Join() unless you want to actually wait for the 2nd thread to complete. It pumps a message loop btw. The point of COM threading support is that you (almost) can completely ignore that you are using non-threadsafe code in multiple threads. No locking at all required. .NET has no equivalent of that. The 'almost' is the rub of course.Vyse
Hi Hans, thanks for pointing that out. BTW, the reason I used CurrentThread.Join() is this MS artical: support.microsoft.com/kb/828988. "If you must use STA threads to create COM components, the STA threads must pump messages regularly. To pump messages for a short time, call the Thread.Join method, as follows: Thread.CurrentThread.Join(100)"Departmentalize
@Charlie. That tripped me up too initially, but your code ends up cleaner if you pump the message loop normally.A1
That kb article is a hack for console mode programs. They don't have a message loop. Using Join() is silly, just making it [MTAThread] is enough to force COM to create an STA thread for the COM component to give it a hospitable home. Albeit that now every call will be marshaled. Surely you already pump in your main thread?Vyse
A
4

As far as Get/Translate/Dispatch in .Net, you should be able to call Application.Run or Dispatcher.Run() (depending on whether you are using winforms or wpf) on your helper thread. This will pump a message loop on that thread calling Get/Trans/Dispatch for you. If you detest the idea of that, then you could P/Invoke the Win32 calls.

While .Net will make almost everything work for you as per hans' answer, in practice I've found that message coming from the COM object can cause your UI thread to stutter as the underlying DispatchMessage() seems to take a long time (certainly longer than I expected, or could account for). We changed our solution to create the COM object on the helper thread and marshalled calls to it from the UI thread explicitly.

A1 answered 21/7, 2011 at 6:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.