20

有时当我结束应用程序并尝试释放一些 COM 对象时,我会在调试器中收到警告:

RaceOnRCWCleanUp被检测到

如果我编写一个使用 COM 对象的类,我是否需要实现IDisposable并调用Marshal.FinalReleaseComObject它们IDisposable.Dispose才能正确释放它们?

如果Dispose没有手动调用,我还需要在终结器中释放它们还是 GC 会自动释放它们?现在我调用Dispose(false)终结器,但我想知道这是否正确。

我使用的 COM 对象也有一个类监听的事件处理程序。显然事件是在另一个线程上引发的,那么如果在处理类时触发它,我该如何正确处理它?

4

3 回答 3

18

首先 - 您永远不必打电话Marshal.ReleaseComObject(...)Marshal.FinalReleaseComObject(...)在进行 Excel 互操作时。这是一个令人困惑的反模式,但任何有关此的信息(包括来自 Microsoft 的)表明您必须从 .NET 手动释放 COM 引用都是不正确的。事实上,.NET 运行时和垃圾收集器正确地跟踪和清理 COM 引用。

其次,如果要确保在进程结束时清理对进程外 COM 对象的 COM 引用(以便 Excel 进程将关闭),则需要确保垃圾收集器运行。您可以通过调用GC.Collect()和正确地做到这一点GC.WaitForPendingFinalizers()。调用两次是安全的, end 确保循环也被清理干净。

第三,在调试器下运行时,局部引用会人为地保持活动状态,直到方法结束(这样局部变量检查才起作用)。因此,调用对于从同一方法GC.Collect()中清除对象无效。rng.Cells您应该将执行 COM 互操作的代码从 GC 清理中拆分为单独的方法。

一般模式是:

Sub WrapperThatCleansUp()

    ' NOTE: Don't call Excel objects in here... 
    '       Debugger would keep alive until end, preventing GC cleanup

    ' Call a separate function that talks to Excel
    DoTheWork()

    ' Now Let the GC clean up (twice, to clean up cycles too)
    GC.Collect()    
    GC.WaitForPendingFinalizers()
    GC.Collect()    
    GC.WaitForPendingFinalizers()

End Sub

Sub DoTheWork()
    Dim app As New Microsoft.Office.Interop.Excel.Application
    Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add()
    Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1")
    app.Visible = True
    For i As Integer = 1 To 10
        worksheet.Cells.Range("A" & i).Value = "Hello"
    Next
    book.Save()
    book.Close()
    app.Quit()

    ' NOTE: No calls the Marshal.ReleaseComObject() are ever needed
End Sub

关于这个问题有很多虚假信息和混淆,包括 MSDN 和 StackOverflow 上的许多帖子。

最终说服我仔细研究并找出正确建议的是这篇文章https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/以及找到在某些 StackOverflow 答案的调试器下,引用保持活动状态的问题。

于 2016-06-29T22:33:49.700 回答
16

根据我使用不同 COM 对象(进程内或进程外)的经验,我建议Marshal.ReleaseComObject每个 COM/.NET 边界跨越一个(如果您引用 COM 对象以检索另一个 COM 引用的实例)。

仅仅因为我决定将 COM 互操作清理推迟到 GC,我就遇到了很多问题。另请注意,我从不使用Marshal.FinalReleaseComObject- 一些 COM 对象是单例的,它不适用于此类对象。

IDisposable禁止在终结器(或众所周知的实现中的 Dispose(false))内的托管对象中执行任何操作。您不能依赖终结器中的任何 .NET 对象引用。您可以 release IntPtr,但不能释放 COM 对象,因为它可能已经被清理了。

于 2013-03-31T14:53:12.990 回答
12

这里有一篇文章:http: //www.codeproject.com/Tips/235230/Proper-Way-of-Releasing-COM-Objects-in-NET

简而言之:

1) Declare & instantiate COM objects at the last moment possible.
2) ReleaseComObject(obj) for ALL objects, at the soonest moment possible.
3) Always ReleaseComObject in the opposite order of creation.
4) NEVER call GC.Collect() except when required for debugging.

直到GC自然发生,com引用才会被完全释放。这就是为什么这么多人需要使用 FinalReleaseComObject() 和 GC.Collect() 来强制对象销毁的原因。脏互操作代码都需要两者。

GC 不会自动调用 Dispose。当一个对象被释放时,析构函数被调用(在不同的线程中)。这通常是您可以释放任何非托管内存或 com 引用的地方。

析构函数:http: //msdn.microsoft.com/en-us/library/66x5fx1b.aspx

...当您的应用程序封装非托管资源(如窗口、文件和网络连接)时,您应该使用析构函数来释放这些资源。当对象符合销毁条件时,垃圾收集器运行对象的 Finalize 方法。

于 2013-03-31T15:01:27.773 回答