代码之家  ›  专栏  ›  技术社区  ›  Brent Arias

是否有时间忽略IDisposable.Dispose?

  •  6
  • Brent Arias  · 技术社区  · 15 年前

    当然,我们应该在不需要IDisposable对象时(这通常只是“using”语句的作用域)立即对其调用Dispose()。如果我们不采取这种预防措施,那么可能会发生一些不好的事情,从微妙到停止。

    但流程终止前的“最后一刻”呢?如果您的IDisposables在那个时间点还没有被明确地处理,那么它不再重要,这不是真的吗?我这样问是因为clr下的非托管资源由内核对象表示,而win32进程终止将释放所有非托管资源/内核对象。换言之,在进程终止后,不会有任何资源仍然“泄漏”(不管是否对延迟的IDisposables调用了Dispose())。

    有人能想到这样一种情况吗:进程终止仍然会留下泄漏的资源,仅仅是因为一个或多个IDisposable上没有显式调用Dispose()?

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

    编辑:那么在Linux上运行mono呢?在清理未管理的“泄漏”时,过程终止是否与“可靠”一样?

    后期编辑:虽然IDisposables可能存在“其他用途”,但我的重点是资源泄漏。我听到了两个答案:(1)如果您的进程拒绝终止,您将有一个泄漏;(2)是的,即使进程终止,资源也可能泄漏。我当然同意第(1)项,尽管它不在我所追求的范围之内。否则,第(2)项正是我要找的,但我不能动摇这种感觉,这只是一个猜测。Jeffrey Richter(“通过C/C++窗口”)解释了(优雅地)终止的Win32进程不会留下泄漏或孤立的资源。为什么包含clr的进程会改变这一点?使用clr时,文档、特定示例或理论方案在哪里可以证明win32进程清理功能受到损害?

    5 回复  |  直到 14 年前
        1
  •  2
  •   Josh    15 年前

    从技术上讲,这完全取决于IDisposable所做的工作。它被用于很多事情,而不仅仅是非托管资源。

    例如,在使用Outlook应用程序时,我构建了一个很好的Outlook API抽象。作为流处理的附件尤其烦人,因为您需要将其保存到一个临时文件中,使用它,然后清理它。

    所以我的抽象是这样的:

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

    对AttachmentStream调用Dispose时,它所基于的临时文件被删除。在这种情况下,如果不调用Dispose,则不会清除临时文件。我在启动时有一个过程来查找这些孤立的文件,但我想我会举个例子来说明这一点。

    实际上,在进程终止时,操作系统只需清理几乎所有包装了某种套接字、句柄或事务的IDisposable实现。但很明显这就像福利。如果可以的话就避免。

        2
  •  2
  •   Aaronaught    15 年前

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

    Object.Finalize 文档:

    在应用程序域关闭期间,将自动对不免除完成的对象(甚至那些仍然可以访问的对象)调用Finalize。

    因此,只要满足以下两个标准,您就可以安全关机:

    • 每个 IDisposable 仍然存在的对象具有正确实现的终结器(对于框架类是正确的,对于不可靠的库可能不是正确的);以及

    • 它实际上是一种协作关闭,而不是异常关闭,例如硬进程终止、控制台应用程序中的ctrl-c,或者 Environment.FailFast .

    如果不满足这两个条件中的任何一个,您的应用程序可能会保持 全球的 非托管资源(如互斥体),实际上会泄漏。因此,最好打电话 Dispose 如果可以的话,早一点。 大多数 有时,您可以依赖clr和对象终结器来为您完成这项工作,但最好是安全而不是抱歉。

        3
  •  1
  •   mmr    15 年前

    我经常发现自己处理的一件事是串行端口——现在,当程序启动时,串行端口应该被释放,而其他程序在被另一个进程持有时不能访问串行端口。所以,如果您的进程拒绝死亡,那么您就是在捆绑一个串行端口。如果用户试图重新启动您的程序,但前一个进程的僵尸版本仍然保留在串行端口上,这可能非常糟糕。

    是的,我以前让我的程序发现自己处于僵尸状态,然后客户抱怨程序不再工作,因为程序重新启动时无法连接到串行端口。结果要么引导用户完成任务管理器中进程的终止,要么让他们重新启动,这两项任务都不是特别用户友好的任务。

        4
  •  0
  •   Nayan    15 年前

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

    这就是为什么我们需要 Dispose 功能。所有释放内存的操作都不是自动的,正如您在非托管代码中所说的那样。

    现在,如果你认为非托管代码是由操作系统自动释放的,那就不是真的了。如果应用程序没有正确地处理,那么有许多句柄和锁可能仍然处于活动状态。

        5
  •  0
  •   Fyodor Soikin    15 年前

    首先,我想指出 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无法理解的——这是使用的地方 不可分的 .

    因此,您不能指望Windows在您之后清理内核对象。可能还有一些东西连窗户都不知道。

    但是,也就是说,如果您的可释放对象被正确地写入(即在终结器中调用Dispose),那么在进程退出时,它们仍将由clr处理。

    所以,答案是:是的,不需要打电话 Dispose 就在项目退出之前。但不是因为可释放资源是内核对象(因为它们不一定是)。


    编辑

    正如JoshEinstein正确指出的,终结器实际上并不能保证在进程退出时运行。那么答案就变成: 总是打电话给Dispose,只是为了起诉