1

正如 MSDN 所说:

如果您正在编写单线程应用程序(或一次只有一个线程访问 DOM 的多线程应用程序),请使用租用线程模型(Msxml2.DOMDocument.3.0 或 Msxml2.DOMDocument.6.0)。如果您正在编写一个应用程序,其中多个线程访问将同时访问一个 DOM,请使用自由线程模型(Msxml2.FreeThreadedDOMDocument.3.0 或 Msxml2.FreeThreadedDOMDocument.6.0)。

FreeThreadedDOMDocument、中性公寓和自由线程编组器之间有什么联系吗?我查看了 OleView,发现 FreeThreadedDOMDocument 线程模型是 Both。据我了解,自由线程封送器支持中性公寓对象。这是否意味着 FreeThreadedDOMDocument 不使用自由线程编组器,并且它被称为自由线程有点令人困惑?

标记为 Free、Both 或 Neutral 的 COM 类之间的实现区别是什么?据我了解,它们都必须是线程安全的,为什么有区别?Neutral 应该支持自由线程编组器是否正确?

4

1 回答 1

3

这里有多个问题。

TL;博士:

中性物体:

  • 比 STA 和 MTA 对象产生更少的进​​程内封送处理
  • 避免线程切换
  • 接口指针被自动封送
  • 只要中性物体存在,中性公寓就会存在
  • 必须准备好在任何类型的线程下运行,使用 COM 实用程序函数等待或根据线程类型选择使用 Win32 等待函数

自由线程对象:

  • 几乎没有进行过程中的编组
  • 避免线程切换
  • 接口指针不会自动封送
  • 终身与激活公寓相关联
  • 必须准备好在任何类型的线程下运行,使用 COM 实用程序函数等待或根据线程类型选择使用 Win32 等待函数

FreeThreadedDOMDocument、中性公寓和自由线程编组器之间有什么联系吗?

TL;DR:FreeThreadedDOMDocument的线程模型是“Both”,因此它与激活(创建)它的单元相关联。它聚合了自由线程封送器,因此它是一个自由线程对象。

FreeThreadedDOMDocument是一个 COM 类,其对象聚合自由线程封送拆收器。 这个封送处理程序所做的是在封送处理过程中提供一个原始指针(即IMarshal::MarshalInterface设置dwDestContextMSHCTX_INPROC.

我将使用自由线程对象的定义作为聚合自由线程封送器的对象。

在 Windows 2000 之前,自由线程对象的线程模型应指定为“中性”或“两者”,因此它可以在任何线程中创建和使用,避免上下文切换。

如果其线程模型被指定为“Both”,则对象的生命周期与创建它的单元相关联。例如,如果 STA 线程终止,则在该单元中创建的所有空闲线程对象要么被破坏,要么不再有效。

据我了解,自由线程封送器支持中性公寓对象。

不,中性对象的代理比其他进程内代理要轻一些,因为它只设置一个 COM 上下文,但它永远不会发生完全封送处理并且它避免了线程切换。

这是否意味着 FreeThreadedDOMDocument 不使用自由线程编组器,并且它被称为自由线程有点令人困惑?

不,FreeThreadedDOMDocument确实使用免费的线程编组器。

从历史上看,在微软为它们提供自己的支持之前已经有免费的线程对象(由于流行,可能是因为那里的大多数免费线程编组器都是不稳定的),并且 Neutral 单元只出现在 Windows 2000 中。

因此,实例FreeThreadedDOMDocument是自由线程的,因为它们聚合了自由线程封送器,并且每个实例的生命周期都与创建它的单元相关联。通常,影响很小,但是对于例如 STA 线程的线程池,这种影响会更频繁地观察到,因为 STA 在拥有线程终止(正常或回收资源)并被创建时来来去去。例如,经典 ASP 默认使用 STA 线程。

PS:我在另一个答案中提到了以下主题,但我相信内容有点不同,因为问题也不同。

这是当前的线程模型值:

  • 无:使用主 STA
  • “公寓”:使用任何STA,即如果当前公寓是STA或NA over STA,则使用当前STA,否则使用主机STA(稍后会详细介绍)
  • “免费”:使用 MTA
  • “Both”:使用当前的公寓
  • “中性”:使用 NA

对于任何不存在的公寓,COM 会在需要时创建它。

这里有几个特点:

  • 要使用主 STA,你不能提到任何线程模型,而不是更明智的东西,比如“Main”
  • 如今,除了“中性”之外的所有名称都毫无意义:
    • “公寓”感觉就像现在的公寓,但不是
    • “免费”感觉就像是免费的线程对象,但它不是
    • “Both”让你以为只有2种公寓类型,其实有3种:STA、MTA和NA
      • 实际上,从 Windows 8 开始,就有了 ASTA,它是为 GUI 创建的 STA 的变体,它在呼出期间丢弃不相关的呼入,从而避免了重入错误的重要来源
      • 您可以使用消息过滤器使常规 STA 表现得像这样

主 STA 是第一个创建的 STA。它只对具有未指定线程模型的类很重要。

可以有多个STA,但最多有一个MTA和一个NA。

虽然有一个活动的 MTA,任何未为 COM 初始化的线程如果不调用 则隐含在 MTA 中CoInitializeEx(NULL, COINIT_MULTITHREADED),但它也不影响 MTA 的生命周期,这意味着 MTA 可能在线程使用时被销毁. 由于这几乎没有记录并且几乎不可靠,因此您不应该依赖它。

隐式创建的公寓称为主机 STA 和主机 MTA。你无法控制他们(除非CoUninitialize在那个公寓里作弊;注意:实际上不要这样做)。事实上,如果您在 STA 之外或在 STA 上运行的 NA 之外激活“Apartment”对象,它将在主机 STA 中激活。为了进一步混淆,如果主机 STA 是要初始化的第一个 STA,这也可能是主 STA。

所有支持主机单元的 COM 线程都是后台线程,因此它们不会阻止您的应用程序退出。

除了在激活中性对象时创建它之外,您无法控制 NA。您不能直接输入它,但您可以使用在中性公寓的上下文中运行回调的方法创建自己的中性对象。这个回调可以是一个自由线程对象。

标记为 Free、Both 或 Neutral 的 COM 类之间的实现区别是什么?

公寓声明为“免费”的 COM 类将生成属于 MTA 的对象。此类对象可能假定它们运行的​​线程不必泵送窗口消息。从本质上讲,它们可能会阻塞。

自由线程对象和中性对象必须准备在任何单元下运行。对于自由线程对象,原因应该很明显:它绕过任何上下文封送处理,因此方法在任何线程中执行。对于中性对象,存在活动类型的区别(通过CoGetApartmentType)。

在任何一种情况下,您都应该使用 COM 的实用程序函数,例如,CoWaitForMultipleHandles而不是,它会阻塞并且在 STA 中是不可接受的,或者,它访问窗口消息队列,可能会隐式创建它,并且通常在 MTA 中是不可接受的。WaitForMultipleHandles[Ex]MsgWaitForMultipleHandles[Ex]

您可以自行检查公寓类型并选择使用正确的 Win32 等待功能或使用轮询策略等待并在 STA 中发送超时消息,以防您在等待句柄以外的其他内容或需要具体的等待逻辑。

自由线程对象和中性对象之间最显着的区别是其他 COM 对象的编组。

在使用中性对象时,传入和传出接口指针会自动编组。例如,您可以将传入的接口指针存储在字段中。

在使用自由线程对象时,传入和传出接口指针根本不会被封送,这意味着您要么获得指向同一单元中对象的原始指针,要么获得指向其他单元中对象的代理。这些代理也与当前公寓相关联。

例如,传入的原始指针意味着您正在获取一个属于当前单元的对象,因此如果您打算存储对该对象的引用,则必须对其进行编组。

传入代理意味着您正在获取另一个公寓中的对象的代理,但此代理与当前公寓绑定。您也无法存储此代理。具体来说,尽管有标准的代理/存根单元验证,STA 代理可能具有线程亲和性。您也必须对其进行编组。但别担心,编组代理不会堆叠编组;当您再次解组时,您将获得对象的代理,而不是代理的代理。

当一个自由线程对象必须存储一个接口指针时,它必须总是通过手动编组来做到这一点,而当它必须从这个接口指针调用方法时,它必须通过手动解组来做到这一点。

通常,全局接口表(GIT;另一个具有误导性的名称,它实际上是一个进程内表)用于此目的。

据我了解,它们都必须是线程安全的,为什么有区别?

关于线程安全,没有区别。

但正如我在上一个问题中解释的那样,存储接口指针时存在巨大差异,而在对象激活和生命周期方面存在细微差异。

Neutral 应该支持自由线程编组器是否正确?

自由线程封送拆收器有效地忽略了单元,因此方法的责任是正确执行、同步和/或锁定。因此,两个单元都必须支持自由线程封送器,它是必须支持每个单元的自由线程对象。

可以使用任何线程模型(包括“中性”)在对象中聚合自由线程封送器。

如果您发现中性单元封送器的上下文设置在某种程度上是一个瓶颈,那么您可以考虑使用自由线程封送器,但代价是手动封送存储的接口指针。如果没有,请使用中性公寓。

于 2016-07-28T10:02:34.740 回答