如果程序意外关闭,IDisposable对象是否会被丢弃?

如果程序意外退出(exception或进程终止)会发生什么? 是否存在程序将终止的此类(或其他)情况,但IDisposable对象将无法正确处理?

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

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

一个样品:

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

d1未配置, d2通常为。 某些类型的exception可能会阻止using块的处理以及某些程序崩溃。 如果原因是电源故障或系统崩溃,当然您无能为力。

如果程序意外退出(例如,您终止进程),则绝对不能保证将调用IDisposable.Dispose方法。 你最好不要依赖它来做这类事件。 Dispose方法必须由您的代码手动调用,这不是CLR会自动为您调用的内容。

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

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

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

 class FinalizableObject { ~FinalizableObject() { Thread.Sleep(50000); Console.WriteLine("Finalized"); } } class Program { static void Main(string[] args) { new FinalizableObject(); } } 

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

因此,您不应该依赖终结器和一次性物体。 但是所有打开的内核对象句柄都会自动关闭,所以你不必担心它们。

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

  1. 每个人都以错误的方式思考垃圾收集(Raymond Chen)
  2. 当你知道的一切都是错的时候,第一部分(Eric Lippert)
  3. 当你知道的一切都是错的时候,第二部分(Eric Lippert)
  4. 终止进程(MSDN)

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

 class DisposableTest : IDisposable { public void Dispose() { Console.WriteLine("Dispose called"); } } ... using (DisposableTest sw = new DisposableTest()) { Thread.Sleep(20000); } 

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

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

[编辑]

刚刚测试了我的好奇心:

  • StackOverflowException使进程终止,因此不会调用Dispose()
  • OutOfMemoryException允许正常调用Dispose()

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

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

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

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

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

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

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

进程关闭不会调用Dispose,并且不能保证终结器运行…所以你必须希望破坏进程本身就足够了。