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

标签 c# com-interop sta mta

虽然有很多关于 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/

相关文章:

c# - 在 .NET 中将属性公开为变体以进行互操作

c# - 为 async void main 设置 ApartmentState

c# - 防止事件产生另一个线程

c# - 如何在一个类中两次使用 "override string ToString()"方法

c# - MDI Form 有焦点吗?

c# - 如何使用静态事件将信息从附加行为传递到 View 模型?

c - 如何从 Native C (win32) 调用 COM Interop DLL

c# - 在 C# 中通过 COM Interop 编码字符串时编码失败(双 UTF8 编码?)

multithreading - 单线程单元中的工作流程 4.0?

c# - Stream.CopyTo 与 Librsync.PatchStream 一起使用时挂起