【问题标题】:Does a COM object get released by the CLR when the thread that created it terminates?当创建 COM 对象的线程终止时,CLR 是否会释放 COM 对象?
【发布时间】:2013-10-15 14:06:06
【问题描述】:

我一直无法弄清楚如何搜索对这种怀疑的确认,但我看到证据表明在一个线程上创建的 COM 对象不再可用于其他线程(已与其底层 RCW 分离的 COM 对象不能使用)一旦代码停止在创建它的线程上执行(并且该线程可能已经终止)。这是一个非常难以追踪的问题,因为我在整个代码中都调用了System.Runtime.InteropServices.Marshal.ReleaseComObject,但我无法确定其中任何一个被调用导致此错误。最后我得出的结论是,当辅助线程停止执行时,COM 对象显然被隐式释放了。这可能是真的吗?这是记录在案的行为吗?

【问题讨论】:

  • 这不应该是真的。您也很少需要使用ReleaseComObject。你确定你使用正确吗?
  • 我正在使用ReleaseComObject,因为如果第 3 方组件中的某些对象检测到库中的另一个对象仍在使用中(他们不希望它们在同时)。因此,至少在某些情况下,我们必须非常确定地发布我们的引用。

标签: c# .net vb.net com com-interop


【解决方案1】:

是的,COM 对象往往具有很强的线程亲和性。线程不是 COM 中的次要实现细节。与 .NET 不同,COM 为 COM 类提供线程安全保证。 COM 可以发布它支持的线程类型,“公寓”(即“非线程安全”)是一个非常常见的选择。 COM 确保满足这些要求,而无需程序提供任何帮助。将一个线程的调用编组到另一个线程以便始终以线程安全的方式使用该对象是自动的。在 .NET 代码中,您通常必须自己执行此操作,例如使用 Control.BeginInvoke 或 Dispatcher.BeginInvoke。

这样做的一个自动结果是拥有一个或多个允许退出的 COM 对象的线程将自动释放这些对象。这是必要的,因为不再有办法满足线程安全要求。在这之后尝试使用它们将会爆炸。除了确保线程存活足够长的时间以继续为这些对象提供服务之外,没有其他方法可以解决这个问题。同样,您需要使 UI 线程保持足够长的时间以确保 Dispatcher.BeginInvoke 仍然可以在 .NET 中工作。

Fwiw,是的,使用 Marshal.ReleaseComObject() 会让您对此感到困惑。显式内存管理产生错误程序的历史由来已久,而自动垃圾收集提供了解决方案。 GC 完全有能力在没有您帮助的情况下释放该 COM 对象,并且永远不会出错。只是需要更长的时间来解决它。如果您知道 COM 对象具有异常高的资源使用率,需要确定性地释放它,那么您就可以为昂贵的 .NET 对象图做同样的事情:GC.Collect() 有助于实现这一点。检查 this answer 了解为什么 Marshal.ReleaseComObject() 往往会被不必要地使用。

【讨论】:

  • 我试图重现错误是一个使用 ADODB 作为示例 COM 服务器的简单程序,但无法这样做。也许那是因为 ADO 不是单元线程。您能否提供任何示例代码来证明此行为与常用的 COM 对象或参考任何描述该行为的官方文档?此外,我明确使用 ReleaseComObject 的原因是因为 SAP(第三方提供商)示例代码和支持工程师建议/要求它,因为它对于他们的一些强制一次性使用对象是“必要的”。 GC.Collect 对于这样的用例来说是不好的形式。
  • 很多关于 COM 的书籍,其中一些仍在印刷中。 this question 中还有另一个 SAP 示例代码的受害者。
  • 事实证明,我并没有成为 ReleaseComObject 滥用的受害者。问题实际上是线程终止时对象的隐式释放,如我其他答案中的示例代码所示。
  • 当然,你只是成为它所造成的混乱的受害者 :) 让我们继续关注这个问题。
  • 您是否真诚地建议在需要 ReleaseComObject 的任何地方调用 GC.Collect(因为 COM 服务器要求在创建新对象之前释放旧对象)?这不是一个严重的性能问题吗?不是有一些例外吗?
【解决方案2】:

这是我设法重现 Hans Passant 回答中的行为的示例代码。我可以单击按钮 1 来创建对象,然后在创建线程终止后单击按钮 2 访问它时,我收到错误消息“无法使用已与其底层 RCW 分离的 COM 对象。”

Public Class Form1

   Dim comRef As Microsoft.Office.Interop.Outlook.Application

   Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
      Dim t As New System.Threading.Thread(AddressOf CreateApplication)
      t.SetApartmentState(Threading.ApartmentState.STA)
      t.Start()
   End Sub

   Private Sub CreateApplication()
      comRef = New Microsoft.Office.Interop.Outlook.Application
   End Sub 

   Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
      TextBox1.Text = comRef.DefaultProfileName
   End Sub
End Class

【讨论】:

    猜你喜欢
    • 2010-11-06
    • 2015-09-25
    • 2012-09-17
    • 2015-07-09
    • 1970-01-01
    • 2012-03-06
    • 1970-01-01
    • 2013-04-10
    相关资源
    最近更新 更多