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

时间:2014-01-30 08:33:41

标签: c# com-interop sta mta

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

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

几个问题:

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

3 个答案:

答案 0 :(得分: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中编组从一个线程到另一个线程的调用。请注意,这并不一定意味着必须有消息循环。如果没有需要编组的调用,或者换句话说,如果你从同一个线程中对组件进行 all 调用,那么就不需要消息循环。这通常很容易保证,特别是在控制台模式应用程序中。当然,如果你自己创建线程,那就不行了。

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

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

答案 1 :(得分:5)

  

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

没有。它在MTA中,因此不需要消息泵。

  

或CLR是否会在新的STA线程上为我创建

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

  

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

取决于

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

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

  

在性能方面更好

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

(请记住,如果你为STA创建线程,然后在本地实例化对象,你需要进行消息抽取。我不清楚是否有任何CLR级轻量级消息泵 - 包括WinForms只是因为这肯定是“T。)

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

答案 2 :(得分:4)

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

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

这种情况通常是不受欢迎的。它有很多缺点,如果COM无法封送对象的某些接口,可能根本无法正常工作。查看this question了解更多详情。此外,由隐式STA公寓运行的消息泵循环仅泵送有限数量的COM特定消息。这也可能会影响COM的功能。

您可以尝试一下,它可能适合您。或者,您可能遇到一些令人不快的问题,比如死锁,很难诊断。

这是我刚才回答的一个密切相关的问题:

StaTaskScheduler and STA thread message pumping

我个人更喜欢手动控制线程间调用和线程关联的逻辑,我的答案中提出了类似ThreadAffinityTaskScheduler的内容。

您可能还想阅读此内容:INFO: Descriptions and Workings of OLE Threading Models,强烈推荐。