如何在 C# 中为 COM STA 线程泵送消息?
我有一个主 STA 线程,它在 COM 对象上调用很多方法,还有一个辅助 STA 线程,它也在同一个对象上执行很多工作。我希望主线程和辅助线程并行工作(即我希望主线程和辅助线程交错输出)。我知道我需要时不时地在主线程中发送消息 - 在 C++ 中调用 Get/Translate/DispatchMessage 就可以了。
但我在 C# 中使用相同的策略时遇到问题。起初,我在主线程中使用 CurrentThread.Join() 将控制权交给第二个线程。它不起作用。然后我转向 Application.DoEvents() - 每当我想要运行第二个线程时,我都会在主线程中调用它。结果是第二个线程快速抓住控制权并且不会放手 - 主线程无法继续,直到第二个线程完成所有操作。
我读到的文档说 Application.DoEvents() 将处理所有等待事件 - 而 GetMessage() 仅检索一条消息。
正确的做法是什么? C# 中是否有与 Get/Translate/DispatchMessage 等效的工具?
谢谢
更新:第二个线程运行太快,向主 STA 线程发送大量 COM 调用消息。我只是在第二个线程中添加了延迟以减慢速度。现在两个线程基本上是并行运行的。 但我仍然想知道是否有 C# 等效的 GetMessage/TranslateMessage/DispatchMessage。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
你原来的C++代码违反了STA合同。其中规定接口指针必须从一个线程编组到另一个线程,以便仅从一个线程进行对该对象的所有调用。这是对单线程 COM 服务器的硬性要求,不这样做就会面临与来自多个线程对非线程安全代码进行调用相关的典型痛苦的风险。使用两个 STA 线程并不能减轻您的这一要求,该对象仅由创建它的线程拥有。第二个线程只是另一个线程,由于服务器不支持多线程,因此无法安全地进行调用。
你以某种方式在 C++ 代码中逃脱了它,很难想象时不时会出现小故障。否则,COM 无法在进程内 COM 服务器上强制执行 STA 协定,而只能在进程外服务器上强制执行。在这种情况下,违反合约会生成 RPC_E_WRONG_THREAD。
无论如何,在 C# 程序中您将不再能逃脱惩罚。 CLR 自动为您封送接口指针。您在第二个线程上进行的调用将被编组到拥有该对象的 STA 线程。仍然存在交错,但只有当第一个线程空闲并重新进入消息循环时才能传递第二个线程的调用。对此没有解决方法,接口指针的 CLR 处理是严格按照规则进行的。
我想这会对您的代码产生很多影响,最大的一个是第二个线程实际上不再完成任何事情。不存在并发性,对对象的所有调用都严格序列化。并且线程安全。您可能最好只从一个线程进行所有调用,这样您就不必规避相当大的死锁风险。作为额外的好处,适当的消息传递不再那么重要。如果第二个线程执行其他关键工作,那么利用 COM 对单线程代码的支持会很有帮助。
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.
至于 .Net 中的 Get/Translate/Dispatch,您应该能够调用 Application.Run 或 Dispatcher.Run() (取决于您使用的是 winforms 还是 wpf)。这将在该线程上泵出一个消息循环,为您调用 Get/Trans/Dispatch。如果您讨厌这个想法,那么您可以 P/Invoke Win32 调用。
虽然 .Net 将使几乎所有内容为您工作 hans' 的回答,在实践中我发现来自 COM 对象的消息可能会导致你的 UI 线程卡顿,因为底层的 DispatchMessage() 似乎需要很长一段时间(肯定比我预期的要长,或者可以解释)。我们更改了解决方案,在辅助线程上创建 COM 对象,并显式地从 UI 线程编组对它的调用。
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.