虽然有很多关于 COM 和 STA/MTA 的问题(例如 here ),但大多数问题都是关于具有 UI 的应用程序。但是,我有以下设置:
- 控制台应用程序,默认情况下是多线程单元(Main() 明确具有
[MTAThread]
属性)。 - 主线程产生一些工作线程。
- 主线程实例化一个单线程 COM 对象。
- 主线程调用 Console.ReadLine() 直到用户点击“q”,之后应用程序终止。
几个问题:
- 很多地方都提到了 need of a message pump for COM objects .我需要为主线程手动创建消息泵,还是 CLR 会在新的 STA 线程上为我创建它,如 this问题建议?
- 只是为了确保 - 假设 CLR 自动创建必要的管道,那么我是否可以在不需要显式同步的情况下从任何工作线程使用 COM 对象?
- 以下哪一项在性能方面更好:
- 让 CLR 负责与 COM 对象的编码。
- 在单独的 STA 线程上显式实例化对象,并让其他线程通过例如一个
ConcurrentQueue
.
最佳答案
这是由 COM 自动完成的。由于您的 COM 对象是单线程的,因此 COM 需要对象的合适位置以确保以线程安全的方式使用它。由于您的主线程不够友好,无法提供此类保证,COM 会自动创建另一个 线程并在该线程上创建对象。该线程还会自动抽水,您无需执行任何操作。您可以看到它是在调试器中创建的。启用非托管调试并查看 Debug + Windows + Threads 窗口。当您越过 new 调用时,您会看到线程被添加。
很好很容易,但它确实有一些后果。首先,COM 组件需要提供代理/ stub 实现。帮助程序代码知道如何序列化方法调用的参数,以便真正的方法调用可以在另一个线程上进行。通常会提供,但并非总是如此。如果缺少 E_NOINTERFACE 异常,您将很难诊断它。有时是 TYPE_E_LIBNOTREGISTERED,这是一个常见的安装问题。
最重要的是,COM 组件上的每个 调用都将被编码。这很慢,编码调用通常比直接调用本身花费很少时间的方法慢 10,000 倍左右。就像一个属性 getter 调用。当然,这确实会使您的程序陷入困境。
STA 线程避免了这种情况,因此是使用单线程组件的推荐方式。是的,STA 线程需要泵送消息循环。 .NET 程序中的 Application.Run()。在 COM 中,消息循环将调用从一个线程编码到另一个线程。请注意,这并不一定意味着您必须有一个消息循环。如果没有调用需要编码,或者换句话说,如果您从同一线程对组件进行所有 调用,则不需要消息循环。这通常很容易保证,尤其是在控制台模式应用程序中。如果您自己创建线程,当然不会。
还有一个更令人讨厌的细节:单线程 COM 组件有时假设它是在一个线程上创建的,该线程会进行泵送。并且将使用 PostMessage() 本身,通常是在它在内部使用工作线程并需要在 STA 线程上引发事件时。当你不抽水时,那当然就不能再正常工作了。您通常会通过注意到没有引发事件来诊断这一点。这种组件的常见示例是 WebBrowser。它在内部大量使用线程,但会在创建它的线程上引发事件。如果您不抽水,您将永远不会获得 DocumentCompleted 事件。
因此,即使不调用 Application.Run(),将 [STAThread] 放在您的 Main() 方法上也可能足以获得快乐的快速代码。请牢记后果,看到方法调用死锁或未引发事件是需要泵送的信号。
关于c# - 从多个线程调用 STA COM 对象的 MTA 控制台应用程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21451313/