是否有时间忽略IDisposable.Dispose?

时间:2010-04-27 02:52:24

标签: c# process dispose idisposable memory-leaks

当然,我们应该在不需要IDisposable对象时调用Dispose()(这通常只是“using”语句的范围)。如果我们不采取这种预防措施,那么可能会发生从细微到显示停止的坏事。

但是在流程终止之前的“最后时刻”呢?如果您的IDisposables在那个时间点没有明确处理,那么它不再重要吗?我问,因为CLR下面的非托管资源由内核对象表示 - 而win32进程终止将释放所有非托管资源/内核对象。换句话说,在进程终止后,没有资源会保持“泄露”(无论是否在延迟的IDisposables上调用Dispose())。

任何人都可以想到一个流程终止仍会留下泄漏资源的情况,只是因为未在一个或多个IDisposable上显式调用Dispose()?

请不要误解这个问题:我并不是在试图忽视IDisposables。问题只是技术理论上的。

编辑:那么在Linux上运行单声道怎么样?在清理未管理的“泄漏”时,流程终止是否“可靠”?

LATE EDIT:虽然IDisposable可能存在“其他用途”,但我的重点是资源泄漏。我听到了两个答案:(1)如果你的进程拒绝终止,你就会泄漏;(2)是的,即使进程终止,资源也可能泄漏。我当然同意第(1)项,尽管它超出了我所追求的范围。否则,第(2)项正是我正在寻找的,但我无法摆脱这只是猜测的感觉。 Jeffrey Richter(“通过C / C ++的Windows”)解释说,(优雅地)终止的Win32进程不会留下泄漏或孤立的资源。为什么包含CLR的进程会改变这种情况?在使用CLR时,文档,特定示例或理论场景在哪里可以说明Win32进程清理功能受到损害?

5 个答案:

答案 0 :(得分:2)

从技术上讲,这一切都取决于IDisposable的作用。它已被用于很多事情,而不仅仅是非托管资源。

例如,在处理Outlook应用程序时,我构建了一个很好的Outlook API抽象。附件对于使用流特别恼人,因为你需要将它保存到临时文件,使用它,然后清理它。

所以我的抽象看起来像这样:

OutlookMailItem mailItem = blah;
using (Stream attachmentStream = mailItem.OpenAttachment("something.txt")) {
   // work with stream
}

在AttachmentStream上调用Dispose时,它所基于的临时文件被删除。在这种情况下,如果未调用Dispose,则永远不会清除临时文件。我在启动时有一个进程来查找这些孤立的文件,但我想我会以此为例。

实际上,几乎所有包装某种套接字,句柄或事务的IDisposable实现都将在进程终止时由操作系统简单地清理。但显然这就像福利。如果可以,请避免使用它。

答案 1 :(得分:2)

在协作关闭期间,AppDomain被卸载,导致所有终结器执行:

来自Object.Finalize文档:

  

在关闭应用程序域期间,会自动调用Finalize,这些对象不会被终结,即使是那些仍然可以访问的对象。

只要满足两个条件,您就可以安全停机:

  • 每个仍然存活的IDisposable对象都有一个正确实现的终结器(对于Framework类来说,对于不太可靠的库可能不是这样);以及

  • 实际上是合作关闭,而不是异常关机,例如硬件进程终止,控制台应用程序中的Ctrl-C或Environment.FailFast

如果未满足这两个标准中的任何一个,则您的应用程序可能会保留全局非托管资源(例如互斥锁),这些资源实际上会泄漏。因此,如果可以,最好尽早致电Dispose大多数当时,你可以依靠CLR和对象终结器为你做这项工作,但最好是安全而不是抱歉。

答案 2 :(得分:1)

我经常发现自己处理的一件事是串口 - 现在,当程序放松时应该释放一个串口,其他程序在被另一个进程占用时无法访问串口。因此,如果您的进程拒绝死亡,那么您将占用一个串行端口。如果用户尝试重新启动程序,那可能会非常糟糕,但前一个进程的僵尸版仍然保留在串行端口上。

是的,我之前让我的程序发现自己处于僵尸状态,然后客户抱怨该程序不再工作,因为程序在重启时无法连接到串口。结果是通过在任务管理器中杀死进程或让他们重新启动来引导用户,这两者都不是特别用户友好的任务。

答案 3 :(得分:0)

您可以编写自定义代码以释放一次性类中的对象。编写代码以释放非托管和托管代码。

这就是我们需要Dispose功能的原因。所有释放内存都不是自动的,就像你在非托管代码中所说的那样。

现在,如果您认为操作系统会自动释放非托管代码,则不然。如果应用程序未正确处理,有许多句柄和锁可能保持活动状态。

答案 4 :(得分:0)

首先,我想指出IDisposable不仅适用于Windows内核对象或非托管内存。相反,它是垃圾收集不理解的东西(哪些内核对象是特殊情况)。

一个小例子,只是为了给你一个想法:

    sealed class Logger : IDisposable
    {
        private bool _disposed = false;

        public Logger()
        {
            System.IO.File.AppendAllText( @"C:\mylog.txt", "LOG STARTED" );
        }

        ~Logger()
        {
            Dispose();
        }

        public void Dispose()
        {
            if ( !_disposed )
            {
                System.IO.File.AppendAllText( @"C:\mylog.txt", "LOG STOPPED" );
                _disposed = true;
            }
        }

        public void WriteMessage( string msg )
        {
            System.IO.File.AppendAllText( @"C:\mylog.txt", "MESSAGE: " + msg );
        }
    }

请注意,此Logger类不“保留”任何内核对象样式的资源。它打开文件并立即关闭它。但是,有这样的逻辑,它应该在对象被销毁时写入“LOG STOPPED”。这个逻辑是GC无法理解的 - 这是使用IDisposable的地方。

因此,您不能依靠Windows清理内核对象。甚至可能还有一些Windows不知道的东西。

然而,如果说你的一次性物品是正确书写的(即在终结器中调用Dispose),它们在处理退出时仍将由CLR处理。

所以,答案是:是的,没有必要在项目退出之前调用Dispose。但不是因为一次性资源是内核对象(因为它们不一定是)。


修改

正如Josh Einstein正确指出的那样,终结者实际上并不能保证在流程退出时运行。那么答案就变成了:总是调用Dispose,只是为了起诉