C# 释放COM对象的正确方法?
有时,当我结束应用程序并尝试释放某些COM对象时,我会在调试器中收到警告: 检测到C# 释放COM对象的正确方法?,c#,.net,com,C#,.net,Com,有时,当我结束应用程序并尝试释放某些COM对象时,我会在调试器中收到警告: 检测到RaceOnRCWCleanUp 如果我编写了一个使用COM对象的类,我是否需要实现IDisposable并在IDisposable.Dispose中调用Marshal.FinalEleaseComObject 如果未手动调用Dispose,我是否仍需要在终结器中释放它们,或者GC是否会自动释放它们?现在我想知道这是否正确 我使用的COM对象还有一个类侦听的事件处理程序。显然,该事件是在另一个线程上引发的,因此,如
RaceOnRCWCleanUp
如果我编写了一个使用COM对象的类,我是否需要实现IDisposable
并在IDisposable.Dispose
中调用Marshal.FinalEleaseComObject
如果未手动调用Dispose
,我是否仍需要在终结器中释放它们,或者GC是否会自动释放它们?现在我想知道这是否正确
我使用的COM对象还有一个类侦听的事件处理程序。显然,该事件是在另一个线程上引发的,因此,如果在处理类时触发该事件,我如何正确处理它?根据我使用不同COM对象(进程内或进程外)的经验,我建议每个COM/.NET边界交叉点使用一个
Marshal.ReleaseComObject
(如果您引用某个实例的COM对象以检索另一个COM引用)
我遇到了很多问题,只是因为我决定将COM互操作清理推迟到GC。
另外,请注意,我从来没有使用过Marshal.FinalReleaseComObject
——一些COM对象是单例的,它不能很好地处理这些对象
禁止在终结器内的托管对象中执行任何操作(或从众所周知的
IDisposable
实现中进行处置(false))。您不能依赖终结器中的任何.NET对象引用。您可以释放IntPtr
,但不能释放COM对象,因为它可能已经被清除。这里有一篇文章:
简言之:
在GC自然发生之前,com引用不会被完全释放。这就是为什么这么多人需要使用FinalReleaseComObject()和GC.Collect()强制销毁对象。脏互操作代码都需要这两种方法
GC不会自动调用Dispose。当释放对象时,会调用析构函数(在不同的线程中)。这通常是您可以释放任何非托管内存或com引用的地方
析构函数:
…当应用程序封装非托管资源(如windows、文件和网络连接)时,应使用析构函数释放这些资源。当对象符合销毁条件时,垃圾收集器将运行对象的Finalize方法
首先-您不必调用Marshal.ReleaseComObject(…)
或Marshal.FinalReleaseComObject(…)
在执行Excel互操作时。这是一种令人困惑的反模式,但任何有关这方面的信息(包括来自Microsoft的信息)都表明您必须从.NET手动释放COM引用是不正确的。事实上,.NET运行时和垃圾收集器正确地跟踪和清理COM引用
其次,如果要确保在进程结束时清除对进程外COM对象的COM引用(以便Excel进程将关闭),则需要确保垃圾收集器运行。通过调用GC.Collect()
和GC.WaitForPendingFinalizers()正确执行此操作
。调用两次是安全的,end可以确保周期也被清理干净
第三,在调试器下运行时,本地引用将被人为地保持活动状态,直到方法结束(以便本地变量检查工作)调用对于从同一方法清理对象(如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的帖子
最后说服我仔细查看并找出正确建议的是这篇文章,以及在StackOverflow答案的调试器下发现引用保持活动状态的问题。这提醒我手动内存管理不是一个好主意。MDA发出警告,因为COM服务中存在潜在的硬崩溃r、 尝试在应用程序关闭时处理任何内容都是毫无意义的,它将在毫秒后完成。GC已经知道如何执行此操作,请避免帮助。如果使用COM对象的类将一直存在到应用程序终止,并且如果COM对象的“文档”要求“调用某种
停用方法”,该怎么办?(我不确定它是做什么的,但我试着不调用它,没有什么不好的事情发生。)为什么我们不应该调用GC.WaitForPendingFinalizers()跟进的GC.Collect()?我自己也不是100%确定,但如果你读了这篇文章,它会说“GC.Collect将停止操作系统中的所有.NET进程以收集垃圾。每次调用GC.Collect时,您可以将垃圾从第0代升级到第1代,然后升级到第2代。垃圾到达第2代后,您必须终止进程以恢复内存。“因此,可能是因为COM对象可能由于强制执行太多GC调用而在GC中停留太久。更频繁的是对GC.Collect的调用会阻止GC及时释放内存。这是因为长寿命对象(即第2代)GC检查对象的频率较低;问题是每次GC跳过对象时都会促进对象的生成(即,对该对象的引用仍然存在),因此,通过调用GC.Collect对象会提前升级,从而诱使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