如何在C#中为COM STA线程提供消息泵?

7
我有一个主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。
2个回答

8
您的原始C++代码违反了STA协议,该协议规定接口指针必须从一个线程封送到另一个线程,以便所有对象调用都只来自一个线程。这对于单线程COM服务器而言是严格要求,否则会在不支持多线程的代码上进行多线程调用,从而导致典型的问题。即使使用两个STA线程也不能解除此要求,对象仅由创建它的线程拥有。第二个线程只是另一个线程,因为服务器不支持多线程,所以无法安全地从中进行调用。
您在C++代码中某种程度上得以逃脱,很难想象没有时不时出现故障。COM无法强制执行进程内COM服务器的STA协议,只能在进程外服务器上执行。在这种情况下,违反契约会生成RPC_E_WRONG_THREAD错误。
总之,在C#程序中,您将不再逃脱。CLR会自动为您封送接口指针。您在第二个线程上进行的调用将被封送到拥有对象的STA线程。仍然存在交错,但只有在第一个线程空闲并重新进入消息循环时,第二个线程的调用才能被传递。没有任何变通方法,CLR处理接口指针严格遵循规则。
这将对您的代码产生许多影响。最大的影响是第二个线程实际上不再有任何作用。没有并发性,所有对象调用都是严格序列化的。而且是线程安全的。您可能最好只从一个线程中进行所有调用,这样就不必围绕死锁的风险进行操作了。这也是一个额外的好处,因为正确的消息泵处理变得不那么关键。如果第二个线程执行其他关键工作,则利用COM支持单线程代码可能会有所帮助。

2
嗨,汉斯,我不仅仅是在逃避 - 我正在使用C++代码中的全局接口表来进行编组。对于C#,CLR会在内部为我执行编组。此外,我了解STA模型中没有真正的并发,因为所有调用都在公寓中序列化。无论如何,现在似乎有两个线程在并行运行,这就是我想要的。但现在我确实怀疑STA多线程的意义 - 它是否真的能够实现任何东西? - Charlie
3
哦,是“交错”让我走错了方向。那么C#代码没有理由表现得有什么不同。如果你不想等待第二个线程完成,就不要使用Thread.Join()方法。顺便说一下,这会产生一个消息循环。COM线程支持的重点是,你(几乎)可以完全忽略你在多个线程中使用非线程安全代码的事实,完全不需要任何锁。.NET没有相当于此的东西。“几乎”是关键。 - Hans Passant
@Charlie。这最初也让我困扰了,但如果您正常地推动消息循环,您的代码最终会更清晰。 - LukeN
1
那篇知识库文章是针对控制台模式程序的黑科技。它们没有消息循环。使用Join()是愚蠢的,只需将其设置为[MTAThread]即可强制COM为COM组件创建STA线程,以给它提供一个宜居的家。尽管现在每个调用都将被编组。你肯定已经在主线程里泵入了吧? - Hans Passant

4
关于在 .Net 中使用 Get/Translate/Dispatch,您应该能够在辅助线程上调用 Application.RunDispatcher.Run()(取决于您是否使用 winforms 或 wpf),这将在该线程上泵送消息循环,为您调用 Get/Trans/Dispatch。如果您不喜欢这个想法,那么您可以 P/Invoke Win32 调用。
虽然 .Net 会根据 hans' 的答案使几乎所有东西都适用于您,但在实践中,我发现来自 COM 对象的消息可能会导致您的 UI 线程出现卡顿,因为底层的 DispatchMessage() 似乎需要很长时间(肯定比我预期的时间长,或者说难以解释)。我们将我们的解决方案更改为在辅助线程上创建 COM 对象,并显式地从 UI 线程进行调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接