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

JSi*_*ris 24 .net c# garbage-collection dispose terminate

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

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

Pat*_*man 13

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

一个样品:

IDisposable d1 = new X();

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

d1.Dispose();
Run Code Online (Sandbox Code Playgroud)

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

  • 有时会编写一个终结器(C#析构函数)来处理人们无法调用`Dispose`的情况,如上面的代码(使用`d1`).在调用`Dispose`的通常情况下,使用`GC.SuppressFinalize(this);`在该方法中,以避免(大部分)具有终结器的性能成本.当然,如果应用程序进程被操作系统主动杀死,这一切都无济于事(详情可能会有所不同). (2认同)

Dar*_*rov 10

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

  • 是的,如果它是FileStream,那么操作系统将保证在进程关闭时释放底层的非托管句柄.但这并不意味着将调用Dispose方法.只是操作系统知道如何在进程退出时回收文件句柄. (11认同)

Yoh*_*all 8

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

您可能知道,Dispose当垃圾收集器收集实现IDisposable接口的对象时,不会调用该方法.但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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


Ale*_*xei 5

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

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

...

using (DisposableTest sw = new DisposableTest())
{
    Thread.Sleep(20000);
}
Run Code Online (Sandbox Code Playgroud)

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

因此,如前所述,当应用程序崩溃或被杀死时,不要依赖于一次性对象.但是,异常应该触发它.我只是想知道是否异常,例如StackOverflowExceptionOutOfMemoryException将始终触发Dispose().

[编辑]

刚刚测试了我的好奇心:

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