如果程序意外关闭,IDisposable 对象是否会被处理掉?

Do IDisposable objects get disposed of if the program is shut down unexpectedly?

如果程序意外退出(异常或进程终止)会怎样?是否存在程序将终止但 IDisposable 对象不会被正确处理的情况(或其他情况)?

我问的原因是因为我正在编写将与外围设备通信的代码,我想确保它不会处于不良状态。

如果程序意外退出(例如您终止进程),绝​​对不能保证 IDisposable.Dispose 方法会被调用。您最好不要在此类事件中依赖它。 Dispose 方法必须由您的代码手动调用,CLR 不会自动为您调用。

如果原因是异常并从 using 块或 try catch finally 块中抛出,它将按应有的方式处理。如果它没有被 using 块捕获,它就不会自动处理(就像应用程序正常关闭时不会处理的那样)。

样本:

IDisposable d1 = new X();

using (IDisposable d2 = new X())
{
    throw new NotImplementedException();
}

d1.Dispose();

d1 没有处理,d2 通常是。某些类型的异常可能会阻止处理 using 块以及某些程序崩溃。如果是电源故障或者系统崩溃,当然也没办法了。

是的,有这样的情况。例如,调用 TerminateProcess、调用 Environment.FailFast 或遇到内部 CLR 错误都会导致进程退出,而不会 运行 任何其他代码。在这种情况下,你能做的最好的事情就是说 "oh well".

即使进程没有意外退出,调用 Dispose 也是手动操作。这不是通过运行时完成的事情,除非当一个对象实现调用 Dispose 的终结器被垃圾收集时。因此,忘记将一次性对象包装在 using 中或导致使对象保持活动状态的内存泄漏是另一种方式 Dispose 可能永远不会被调用。

唯一可靠的清理是由操作系统在进程退出时执行的 -- 所有打开的系统对象句柄都已关闭。当最后一个句柄关闭时,在 OS 或驱动程序中执行的任何清理都会发生。如果此清理代码不是驱动程序的一部分,而是应该由用户进程调用,那么您所能做的就是使您的代码尽可能健壮,或者实施一个为您处理清理的看门狗进程。

一个使用控制台应用程序的非常简单的测试表明,进程终止时不会调用 Dispose:

class DisposableTest : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Dispose called");
    }
}

...

using (DisposableTest sw = new DisposableTest())
{
    Thread.Sleep(20000);
}

使用任务管理器终止进程不会触发 Disposable.Dispose() 方法。等待20秒即可。

因此,如前所述,当应用程序崩溃或被杀死时,不要依赖一次性对象。但是,异常应该触发它。我只是想知道 WhosebugExceptionOutOfMemoryException 之类的异常是否总是会触发 Dispose().

[编辑]

刚刚测试了我的好奇心:

  • WhosebugException 终止进程,因此没有调用 Dispose()
  • OutOfMemoryException 允许正常调用 Dispose()

IDisposable 只是一个接口。它们的处理方式绝对没有什么特别之处。当您在 IDisposable 上调用 Dispose(显式地或通过 using 块)时,它会调用您的 Dispose 方法的内容。它像任何其他对象一样被垃圾收集。

该接口的目的是允许实施者定义可能具有需要显式清理的托管或非托管资源的类型的清理。

如果这些资源都被管理,垃圾收集可能就足够了,实现可能只是为了优化。

如果它们是非托管的或与非托管资源有某种联系,垃圾收集可能是不够的。这就是为什么 IDisposable 的完整推荐实现涉及处理显式处置和 运行 时间处置(通过终结器)。

进程关闭不会调用 Dispose 并且不能保证终结器 运行...因此您必须希望销毁进程本身就足够了。

除了 Patrick Hofman 和 Alexei 的回答之外,即使应用程序正确终止,清理也可能不会执行。

正如您可能知道的那样,当垃圾收集器收集实现了 IDisposable 接口的对象时,不会调用 Dispose 方法。但是 GC 将调用 Finalize 方法,也称为终结器。您应该在其中使用 Dispose Pattern 编写清理逻辑。是的,.Net Framework 将尝试 运行 所有终结器,但不保证它们会被执行。

例如,下面的程序有很长的 运行ning 终结器。因此,.Net 将终止该进程,您将永远不会看到该消息。

class FinalizableObject
{
    ~FinalizableObject()
    {
        Thread.Sleep(50000);
        Console.WriteLine("Finalized");
    }
}

class Program
{
    static void Main(string[] args)
    {
        new FinalizableObject();
    }
}

这可能是由任何长时间的 运行ning 操作引起的,例如释放网络句柄或其他需要很多时间的操作。

因此,永远不要依赖终结器和一次性对象。但是所有打开的内核对象句柄都会自动关闭,所以你不用担心。

除了答案之外,我还建议您阅读几篇关于终结器和 GC 的有趣文章:

  1. Everybody thinks about garbage collection the wrong way (Raymond Chen)
  2. When everything you know is wrong, part one (Eric Lippert)
  3. When everything you know is wrong, part two (Eric Lippert)
  4. Terminating a Process (MSDN)