4

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

4

2 回答 2

6

是的,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() 有助于实现这一点。检查这个答案,了解为什么 Marshal.ReleaseComObject() 往往会被不必要地使用。

于 2013-10-08T16:26:23.353 回答
0

这是我设法重现 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
于 2013-10-08T16:56:09.727 回答