有时当我结束应用程序并尝试释放一些COM对象时,我会在调试器中收到警告:
检测到
RaceOnRCWCleanUp
如果我编写一个使用COM对象的类,我是否需要在IDisposable
中实现Marshal.FinalReleaseComObject
并调用IDisposable.Dispose
来正确释放它们?
如果未手动调用Dispose
,那么我是否还需要在终结器中释放它们,还是GC会自动释放它们?现在我call Dispose(false)
in the finalizer,但我想知道这是否正确。
我使用的COM对象也有一个类侦听的事件处理程序。显然,事件是在另一个线程上引发的,所以如果在处理类时触发它,如何正确处理呢?
答案 0 :(得分:14)
根据我使用不同COM对象(进程内或进程外)的经验,我建议每个COM / .NET边界跨越一个Marshal.ReleaseComObject
(如果对于实例,您按顺序引用COM对象检索另一个COM引用。)
我遇到了很多问题,因为我决定将COM互操作清理推迟到GC。
另请注意,我从不使用Marshal.FinalReleaseComObject
- 某些COM对象是单例,并且它不能很好地处理这些对象。
禁止在终结器内的托管对象中执行任何操作(或从众所周知的IDisposable
实现中执行Dispose(false))。您不能依赖终结器中的任何.NET对象引用。您可以释放IntPtr
,但不能释放COM对象,因为它已经可以清理。
答案 1 :(得分:9)
这里有一篇文章: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方法。
答案 2 :(得分:7)
首先 - 在执行Excel互操作时,从不必须致电Marshal.ReleaseComObject(...)
或Marshal.FinalReleaseComObject(...)
。这是一个令人困惑的反模式,但是有关此的任何信息,包括来自Microsoft,表明您必须从.NET手动释放COM引用的任何信息都是不正确的。事实是.NET运行时和垃圾收集器正确地跟踪和清理COM引用。
其次,如果要确保在进程结束时清除对进程外COM对象的COM引用(以便Excel进程关闭),则需要确保垃圾收集器运行。您可以通过拨打GC.Collect()
和GC.WaitForPendingFinalizers()
来正确执行此操作。调用两次是安全的,结束确保周期也被清理干净。
第三,当在调试器下运行时,本地引用将被人为地保持活动直到方法结束(以便局部变量检查起作用)。因此,GC.Collect()
调用对于从同一方法清除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上的许多帖子。
最终让我深入了解并找出正确建议的是这篇文章https://blogs.msdn.microsoft.com/visualstudio/2010/03/01/marshal-releasecomobject-considered-dangerous/以及在一些StackOverflow答案的调试器下发现引用保持活动的问题。