Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 757
Charlie Avatar asked Jul 21 '11 04:07

Charlie


2 Answers

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.

like image 153
Hans Passant Avatar answered Sep 18 '22 11:09

Hans Passant


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.

like image 44
LukeN Avatar answered Sep 21 '22 11:09

LukeN