405

你能用你自己的话解释一下 STA 和 MTA 吗?

另外,什么是单元线程,它们是否只与 COM 相关?如果是这样,为什么?

4

9 回答 9

371

COM 线程模型称为“公寓”模型,其中初始化 COM 对象的执行上下文与单个线程(单线程单元)或多个线程(多线程单元)相关联。在此模型中,COM 对象一旦在一个单元中初始化,在其运行期间就是该单元的一部分。

STA 模型用于非线程安全的 COM 对象。这意味着他们不处理自己的同步。它的一个常见用途是 UI 组件。因此,如果另一个线程需要与对象交互(例如按下表单中的按钮),则消息将被编组到 STA 线程上。Windows 窗体消息泵系统就是一个例子。

如果 COM 对象可以处理自己的同步,则可以在允许多个线程与对象交互而无需编组调用的情况下使用 MTA 模型。

于 2008-09-24T13:48:48.097 回答
215

这完全取决于如何处理对对象的调用,以及它们需要多少保护。COM 对象可以要求运行时保护它们不被多个线程同时调用;那些不能从不同线程并发调用的,所以他们必须保护自己的数据。

此外,如果从用户界面线程进行调用,运行时还必须防止 COM 对象调用阻塞用户界面。

公寓是对象居住的地方,它们包含一个或多个线程。公寓定义了拨打电话时会发生什么。对单元中对象的调用将在该单元中的任何线程上接收和处理,但已在正确单元中的线程的调用由其自身处理(即直接调用对象)除外。

线程可以在单线程单元中(在这种情况下,它们是该单元中的唯一线程)或在多线程单元中。它们指定线程何时为该线程初始化 COM。

STA 主要是为了与绑定到特定线程的用户界面兼容。STA通过接收到隐藏窗口的窗口消息来接收调用进程的通知;当它进行出站调用时,它会启动一个模态消息循环,以防止处理其他窗口消息。您可以指定要调用的消息过滤器,以便您的应用程序可以响应其他消息。

相比之下,所有 MTA 线程都为进程共享一个 MTA。如果没有可用的线程,COM 可能会启动一个新的工作线程来处理传入呼叫,直至达到池限制。进行出站调用的线程只是阻塞。

为简单起见,我们将只考虑在 DLL 中实现的对象,这些对象通过设置ThreadingModel类键的值在注册表中通告它们所支持的内容。有四个选项:

  • 主线程(ThreadingModel值不存在)。该对象在主机的主 UI 线程上创建,所有调用都编组到该线程。类工厂只会在该线程上被调用。
  • Apartment. 这表明该类可以在任何单线程模式线程上运行。如果创建它的线程是 STA 线程,则该对象将在该线程上运行,否则它将在主 STA 中创建 - 如果不存在主 STA,将为它创建一个 STA 线程。(这意味着创建单元对象的 MTA 线程将把所有调用编组到不同的线程。)类工厂可以由多个 STA 线程同时调用,因此它必须保护其内部数据免受这种情况的影响。
  • Free. 这表示设计为在 MTA 中运行的类。即使由 STA 线程创建,它也将始终加载到 MTA 中,这再次意味着 STA 线程的调用将被编组。这是因为一个Free对象通常是在编写时期望它可以阻塞的。
  • Both. 这些类是灵活的,可以加载到它们创建的任何公寓中。但是,它们的编写必须满足两组要求:它们必须保护其内部状态免受并发调用,以防它们被加载到 MTA 中,但不能阻塞,以防它们被加载到 STA 中。

从 .NET Framework 开始,基本上只[STAThread]在创建 UI 的任何线程上使用。工作线程应该使用 MTA,除非他们要使用 -Apartment标记的 COM 组件,在这种情况下,如果从多个线程调用相同的组件(因为每个线程必须等待组件依次)。如果您对每个线程使用单独的 COM 对象,无论组件是在 STA 还是 MTA 中,这一切都会变得容易得多。

于 2008-09-24T14:04:59.087 回答
87

我发现现有的解释太gobbledygook。这是我用简单的英语解释:

STA:如果一个线程创建了一个设置为 STA 的 COM 对象(在调用 CoCreateXXX 时,您可以传递一个将 COM 对象设置为 STA 模式的标志),那么只有这个线程可以访问这个 COM 对象(这就是 STA 的意思 - 单线程公寓),试图调用此 COM 对象上的方法的其他线程在后台默默地转为向创建(拥有)该 COM 对象的线程传递消息。这很像只有创建 UI 控件的线程才能直接访问它。这种机制旨在防止复杂的锁定/解锁操作。

MTA:如果一个线程创建了一个设置为 MTA 的 COM 对象,那么几乎每个线程都可以直接调用它的方法。

这就是它的要点。尽管从技术上讲,有些细节我没有提到,例如在“STA”段落中,创建者线程本身必须是 STA。但这几乎是您要了解 STA/MTA/NA 所需要知道的全部内容。

于 2010-07-02T04:00:33.193 回答
27

STA(单线程单元)基本上是一次只有一个线程与您的代码交互的概念。通过 Windows 消息(使用不可见)窗口对进入您公寓的呼叫进行编组。这允许调用排队并等待操作完成。

MTA(多线程单元)是许多线程可以同时运行的地方,作为开发人员,您有责任处理线程安全性。

关于 COM 中的线程模型还有很多需要了解,但是如果您无法理解它们是什么,那么我会说了解 STA 是什么以及它是如何工作的将是最好的起点,因为大多数 COM 对象都是 STA。

单元线程,如果一个线程与其正在使用的对象位于同一个单元中,那么它就是一个单元线程。我认为这只是一个 COM 概念,因为它只是谈论与之交互的对象和线程的一种方式……</p>

于 2008-09-24T13:56:52.617 回答
21

每个承载 COM 或 OLE 控件的 EXE 都定义了它的公寓状态。公寓状态默认是 STA(对于大多数程序来说应该是 STA)。

STA - 所有 OLE 控件都必须存在于 STA 中。STA 意味着您的 COM 对象必须始终在 UI 线程上进行操作,并且不能传递给其他线程(很像 MFC 中的任何 UI 元素)。但是,您的程序仍然可以有很多线程。

MTA - 您可以在程序中的任何线程上操作 COM 对象。

于 2008-09-24T13:44:10.540 回答
13

据我了解,“公寓”用于保护 COM 对象免受多线程问题的影响。

如果 COM 对象不是线程安全的,则应将其声明为 STA 对象。然后只有创建它的线程才能访问它。创建线程应将自己声明为 STA 线程。在后台,线程将 STA 信息存储在其 TLS(线程本地存储)中。我们将此行为称为线程进入 STA 单元。当其他线程想要访问这个 COM 对象时,它应该封送对创建线程的访问。基本上,创建线程使用消息机制来处理入站调用。

如果 COM 对象是线程安全的,则应将其声明为 MTA 对象。MTA 对象可以被多线程访问。

于 2011-04-21T03:11:25.020 回答
5

调用 COM 对象 dll 的代码(例如,读取专有数据文件)可能在用户界面中运行良好,但在服务中神秘挂起。原因是从 .Net 2.0 开始,用户界面假定为 STA(线程安全),而服务假定为 MTA((在此之前,服务假定为 STA)。必须为服务中的每个 COM 调用创建一个 STA 线程会增加大量开销。

于 2014-05-30T14:19:42.547 回答
0

旁注:如果您使用某些 PowerShell 2.0 管理单元,则需要使用 -MTA 选项启动 PowerShell 版本 3 或更高版本才能使用它们。PowerShell 2 单元模型是 MTA,而更高版本默认使用 STA。另一点是位。公寓中的正常呼叫没有编组(直接呼叫),因此如果您的呼叫者是 x64,那么被呼叫者也必须是 x64。解决此问题的唯一方法是使用远程过程调用 (RPC),这会增加大量开销(生成一个新的 32 位进程以通过某种方式加载管理单元 DLL 和查询结果)。对于开发人员:始终发布类型库 -它使您的 COM 对象发现和使用更加容易!每个接口都应该是公共的和唯一的——实现可以是专有的或开源的。

另一种情况

例子:

 IStorage_vtbl** reference; // you got it by some means of factory
 
 
 public unsafe int OpenStorage(char* pwcsName, IStorage pstgPriority, uint grfMode, char** snbExclude, uint reserved, IStorage* ppstg)
 {
     IStorage_vtbl** @this = (IStorage_vtbl**)reference;
     IStorage_vtbl* vtbl = *@this;
     if (vtbl == null)
         throw new InvalidComObjectException();
     Delegate genericDelegate = Marshal.GetDelegateForFunctionPointer(vtbl->method_6, typeof(delegate_6));
     delegate_6 method = (delegate_6)genericDelegate;
     return method(@this, pwcsName, pstgPriority, grfMode, snbExclude, reserved, ppstg);
 }
 

这段代码只是添加了实例的“this”指针,以便真正调用 COM 子系统 那么,这个调用是打开 IStorage STA 还是 MTA 的实例?

于 2021-07-06T13:03:15.450 回答
0

这篇文章非常清楚地解释了 STA & MTA。

了解 COM 公寓,第一部分
了解 COM 公寓,第二部分

关于什么是公寓的要点:

  • 公寓是并发边界;它是一个围绕对象和客户端线程绘制的假想框,用于分隔 COM 客户端和具有不兼容线程特性的 COM 对象。
  • 每个使用 COM 的线程以及这些线程创建的每个对象都分配给一个单元。
  • 当一个线程调用 COMCoInitializeCoInitializeEx函数时,该线程被放置在一个单元中。当一个对象被创建时,它也被放置在一个公寓里。
  • 每当创建一个新公寓时,COM 都会在堆上分配一个公寓对象,并使用公寓 ID 和公寓类型等重要信息对其进行初始化。当它为一个单元分配一个线程时,COM 将相应的单元对象的地址记录在线程本地存储 (TLS) 中。
于 2022-03-02T04:27:47.470 回答