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

本文关键字:是否 处理 IDisposable 对象 程序 意外 如果 | 更新日期: 2023-09-27 18:28:06

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

我之所以这么问,是因为我正在编写与外围设备通信的代码,我想确保它不会处于糟糕的状态。

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

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

示例:

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

d1未被处理,d2通常被处理。某些类型的异常可能会阻止对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. 每个人都认为垃圾收集是错误的(陈)
  2. 当你所知道的一切都错了,第一部分(埃里克·利珀特)
  3. 当你所知道的一切都错了,第二部分(埃里克·利珀特)
  4. 终止进程(MSDN)

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

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

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

所以,正如前面提到的,当应用程序崩溃或被终止时,不要依赖于可丢弃的对象。但是,异常应该触发它。我只是想知道像StackOverflowExceptionOutOfMemoryException这样的异常是否总是会触发Dispose()。

[编辑]

刚刚测试了我的好奇心:

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

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

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

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

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

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

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

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

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