MTA控制台应用程序从多个线程调用STA COM对象

bav*_*aza 11 c# mta com-interop sta

虽然有很多关于COM和STA/MTA的问题(例如这里),但大多数人都在讨论具有UI的应用程序.但是,我有以下设置:

  • 控制台应用程序,默认情况下为Multi-Threaded Apartment(Main()显式具有该[MTAThread]属性).
  • 主线程产生一些工作线程.
  • 主线程实例化单线程COM对象.
  • 主线程调用Console.ReadLine(),直到用户点击'q',然后应用程序终止.

几个问题:

  • 许多地方都提到需要为COM对象提供消息泵.我是否需要为主线程手动创建消息泵,或者CLR是否会在新的STA线程上为我创建消息泵,正如这个问题所暗示的那样?
  • 只是为了确保 - 假设CLR自动创建必要的管道,然后我可以使用来自任何工作线程的COM对象而无需显式同步吗?
  • 在性能方面,以下哪项更好:
    • 让CLR负责处理COM对象的编组.
    • 在单独的STA线程上显式实例化对象,并让其他线程通过例如a与之通信ConcurrentQueue.

Han*_*ant 10

这是由COM自动完成的.由于COM对象是单线程的,因此COM 需要一个适合该对象的主页,以确保它以线程安全的方式使用.由于您的主线程不够友好以提供此类保证,COM会自动创建另一个线程并在该线程上创建对象.这个线程也自动泵送,你无需做任何帮助.您可以在调试器中看到它正在创建.启用非托管调试并查看Debug + Windows + Threads窗口.当您跳过呼叫时,您会看到线程被添加.

很好,很容易,但确实有一些后果.首先,COM组件需要提供代理/存根实现.帮助程序代码知道如何序列化方法调用的参数,以便可以在另一个线程上进行真正的方法调用.这通常是提供的,但并非总是如此.如果缺少E_NOINTERFACE异常,您将很难诊断它.有时TYPE_E_LIBNOTREGISTERED,常见的安装问题.

最重要的是,COM组件上的每个调用都将被编组.这很慢,编组调用通常比直接调用方法慢大约10,000倍,该方法本身只需要很少的时间.就像一个属性getter call.这当然可以让你的程序陷入困境.

STA线程避免了这种情况,因此是使用单线程组件的推荐方法.是的,STA线程需要泵送消息循环.Application.Run()在.NET程序中.它是消息循环,它在COM中编组从一个线程到另一个线程的调用.请注意,这并不一定意味着您必须有一个消息循环.如果没有需要编组的调用,或者换句话说,如果您从同一个线程对组件进行所有调用,则不需要消息循环.这通常很容易保证,特别是在控制台模式应用程序中.当然,如果你自己创建线程的话.

另一个令人讨厌的细节:单线程COM组件有时会假设它是在泵送的线程上创建的.并且将使用PostMessage()本身,通常是在内部使用工作线程并且需要在STA线程上引发事件时.当你不抽水时,这当然不会正常工作.您通常会通过注意到没有引发事件来诊断它.这种组件的常见示例是WebBrowser.其中内部大量使用线程,但在创建它的线程上引发事件.如果你不抽水,你永远不会得到DocumentCompleted事件.

因此,即使没有调用Application.Run(),将[STAThread]放在Main()方法上也可能足以获得快乐的快速代码.只要记住后果,看到一个方法调用死锁或一个没有被提升的事件就是需要抽水的标志.

  • 没有。唯一重要的是方法调用是否由创建对象的线程进行。那总是线程安全的。究竟如何使它成为 STA 确实很重要。主线程是特殊的,Windows 在启动进程时创建它。所以需要 [STAThread] 属性。您启动的任何线程都需要 Thread.SetApartmentState()。 (2认同)

nos*_*tio 5

是的,可以从MTA线程创建STA COM 对象。

在这种情况下,COM(而不是CLR)将创建一个隐式 STA 单元(一个单独的 COM 拥有的线程)或重用现有的一个,更早创建。COM 对象将在那里实例化,然后将为其创建线程安全代理对象(COM 编组包装器)并将其返回给 MTA 线程。在 MTA 线程上对对象的所有调用都将由 COM 编组到该隐式 STA 单元。

这种情况通常是不可取的。它有很多缺点,如果 COM 无法编组对象的某些接口,则它可能根本无法按预期工作。检查此问题以获取更多详细信息。此外,由隐式 STA 单元运行的消息泵循环仅泵送有限数量的 COM 特定消息。这也可能会影响 COM 的功能。

您可以尝试一下,它可能对您很有效。或者,您可能会遇到一些令人不快的问题,例如死锁,很难诊断。

这是我最近回答的一个密切相关的问题:

StaTaskScheduler 和 STA 线程消息泵

我个人更喜欢手动控制线程间调用和线程关联的逻辑,就像ThreadAffinityTaskScheduler我的答案中提出的那样。

您可能还想阅读以下内容:强烈推荐的信息:OLE 线程模型的描述和工作原理


Ric*_*ard 5

我是否需要为主线程手动创建消息泵,

不,它在MTA中,因此不需要消息泵.

或者CLR会在新的STA线程上为我创建它

如果COM创建了线程(因为进程中没有STA),那么它还会创建消息泵(以及隐藏窗口:可以使用SPY ++和类似的调试工具查看).

来自任何工作线程的COM对象,无需显式同步

要看.

如果在MTA中创建了对单线程对象(STO)的引用,那么COM将提供适当的代理.此代理适用于MTA中的所有线程.

在任何其他情况下,需要对引用进行编组以确保它具有正确的代理.

在性能方面更好

唯一的答案是测试两者并进行比较.

(记住,如果你为STA创建线程然后在本地实例化你需要做消息的话.我不清楚是否有任何CLR级轻量级消息泵 - 包括WinForms只是为了这个肯定不是. )

NB.COM和CLR的唯一深入解释性覆盖范围是.NET和COM: Adam Nathan 的完整互操作性指南(Sams,2002年1月).但它基于.NET 1.1,现在绝版(但有一个Kindle版本,可通过Safari Books Online获得).即使这本书也没有直接描述你想要做什么.我建议做一些原型设计.