你能用你自己的话解释一下 STA 和 MTA 吗?
另外,什么是单元线程,它们是否只与 COM 相关?如果是这样,为什么?
COM 线程模型称为“公寓”模型,其中初始化 COM 对象的执行上下文与单个线程(单线程单元)或多个线程(多线程单元)相关联。在此模型中,COM 对象一旦在一个单元中初始化,在其运行期间就是该单元的一部分。
STA 模型用于非线程安全的 COM 对象。这意味着他们不处理自己的同步。它的一个常见用途是 UI 组件。因此,如果另一个线程需要与对象交互(例如按下表单中的按钮),则消息将被编组到 STA 线程上。Windows 窗体消息泵系统就是一个例子。
如果 COM 对象可以处理自己的同步,则可以在允许多个线程与对象交互而无需编组调用的情况下使用 MTA 模型。
这完全取决于如何处理对对象的调用,以及它们需要多少保护。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 中,这一切都会变得容易得多。
我发现现有的解释太gobbledygook。这是我用简单的英语解释:
STA:如果一个线程创建了一个设置为 STA 的 COM 对象(在调用 CoCreateXXX 时,您可以传递一个将 COM 对象设置为 STA 模式的标志),那么只有这个线程可以访问这个 COM 对象(这就是 STA 的意思 - 单线程公寓),试图调用此 COM 对象上的方法的其他线程在后台默默地转为向创建(拥有)该 COM 对象的线程传递消息。这很像只有创建 UI 控件的线程才能直接访问它。这种机制旨在防止复杂的锁定/解锁操作。
MTA:如果一个线程创建了一个设置为 MTA 的 COM 对象,那么几乎每个线程都可以直接调用它的方法。
这就是它的要点。尽管从技术上讲,有些细节我没有提到,例如在“STA”段落中,创建者线程本身必须是 STA。但这几乎是您要了解 STA/MTA/NA 所需要知道的全部内容。
STA(单线程单元)基本上是一次只有一个线程与您的代码交互的概念。通过 Windows 消息(使用不可见)窗口对进入您公寓的呼叫进行编组。这允许调用排队并等待操作完成。
MTA(多线程单元)是许多线程可以同时运行的地方,作为开发人员,您有责任处理线程安全性。
关于 COM 中的线程模型还有很多需要了解,但是如果您无法理解它们是什么,那么我会说了解 STA 是什么以及它是如何工作的将是最好的起点,因为大多数 COM 对象都是 STA。
单元线程,如果一个线程与其正在使用的对象位于同一个单元中,那么它就是一个单元线程。我认为这只是一个 COM 概念,因为它只是谈论与之交互的对象和线程的一种方式……</p>
每个承载 COM 或 OLE 控件的 EXE 都定义了它的公寓状态。公寓状态默认是 STA(对于大多数程序来说应该是 STA)。
STA - 所有 OLE 控件都必须存在于 STA 中。STA 意味着您的 COM 对象必须始终在 UI 线程上进行操作,并且不能传递给其他线程(很像 MFC 中的任何 UI 元素)。但是,您的程序仍然可以有很多线程。
MTA - 您可以在程序中的任何线程上操作 COM 对象。
据我了解,“公寓”用于保护 COM 对象免受多线程问题的影响。
如果 COM 对象不是线程安全的,则应将其声明为 STA 对象。然后只有创建它的线程才能访问它。创建线程应将自己声明为 STA 线程。在后台,线程将 STA 信息存储在其 TLS(线程本地存储)中。我们将此行为称为线程进入 STA 单元。当其他线程想要访问这个 COM 对象时,它应该封送对创建线程的访问。基本上,创建线程使用消息机制来处理入站调用。
如果 COM 对象是线程安全的,则应将其声明为 MTA 对象。MTA 对象可以被多线程访问。
调用 COM 对象 dll 的代码(例如,读取专有数据文件)可能在用户界面中运行良好,但在服务中神秘挂起。原因是从 .Net 2.0 开始,用户界面假定为 STA(线程安全),而服务假定为 MTA((在此之前,服务假定为 STA)。必须为服务中的每个 COM 调用创建一个 STA 线程会增加大量开销。
旁注:如果您使用某些 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 的实例?
这篇文章非常清楚地解释了 STA & MTA。
关于什么是公寓的要点:
- 公寓是并发边界;它是一个围绕对象和客户端线程绘制的假想框,用于分隔 COM 客户端和具有不兼容线程特性的 COM 对象。
- 每个使用 COM 的线程以及这些线程创建的每个对象都分配给一个单元。
- 当一个线程调用 COM
CoInitialize
或CoInitializeEx
函数时,该线程被放置在一个单元中。当一个对象被创建时,它也被放置在一个公寓里。- 每当创建一个新公寓时,COM 都会在堆上分配一个公寓对象,并使用公寓 ID 和公寓类型等重要信息对其进行初始化。当它为一个单元分配一个线程时,COM 将相应的单元对象的地址记录在线程本地存储 (TLS) 中。