正确的释放COM对象的方法?

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

检测到RaceOnRCWCleanUp

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

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

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

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

我遇到了很多问题只是因为我决定将COM互操作清理推迟到GC。 另请注意,我从不使用Marshal.FinalReleaseComObject – 一些COM对象是单例,并且它不能很好地处理这些对象。

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

这里有一篇文章: 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()强制对象销毁的原因。 两者都是脏Interop代码所必需的。

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

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

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

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

其次,如果要确保在进程结束时清除对进程外COM对象的COM引用(以便Excel进程关闭),则需要确保垃圾收集器运行。 通过调用GC.Collect()GC.WaitForPendingFinalizers()正确执行此操作。 调用两次是安全的,结束确保周期也被清理干净。

第三,当在调试器下运行时,本地引用将被人为地保持活动直到方法结束(以便局部变量检查工作)。 因此GC.Collect()调用对于从同一方法清除rng.Cells对象rng.Cells 。 您应该将执行COM interop的代码从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上的许多post。

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