【问题标题】:Do IDisposable objects get disposed of if the program is shut down unexpectedly?如果程序意外关闭,IDisposable 对象是否会被丢弃?
【发布时间】:2016-03-31 13:46:16
【问题描述】:

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

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

【问题讨论】:

  • 您所说的“关闭”具体是什么意思?你的意思是如果电源关闭或抛出未处理的异常?
  • 异常。我知道如果电源关闭(无论如何外围设备将在这种情况下重置),则无法采取任何合理的措施。我会更新问题。
  • @cFrozenDeath 另外,我的意思是当操作系统(或用户)终止进程时。
  • 根据 Unix 系统的定义,当进程收到终止信号时,它会立即被终止,没有任何清理机会,因此肯定是的。
  • 如果外围设备是某种设备并且其状态非常重要,您几乎需要编写操作系统驱动程序。程序总是死掉。它们可能会出现卡住并被用户杀死。或者死于内存不足错误。

标签: c# .net garbage-collection dispose terminate


【解决方案1】:

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

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

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

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

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

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

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

除了答案之外,我建议你阅读一些关于终结器和 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)

【讨论】:

    【解决方案2】:

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

    一个样本:

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

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

    【讨论】:

    • 有时会编写一个终结器(C# 析构函数)来处理人们未能像上面的代码(使用d1)那样调用Dispose 的情况。在调用Dispose 的通常情况下,在该方法中使用GC.SuppressFinalize(this); 以避免(大部分)具有终结器的性能成本。当然,如果应用程序进程被操作系统主动终止,这些都无济于事(此处的详细信息可能会有所不同)。
    • 我的意思是“操作系统”。
    • 即使没有发生崩溃,也接收到终止信号不会让您什么都不做。
    • 确实如此。崩溃将阻止任何操作。
    • @PatrickHofman 但是如果进程在异常发生后立即关闭,则无需处理任何内容,导致整个进程立即关闭,对吧?!
    【解决方案3】:

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

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

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

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

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

    【讨论】:

      【解决方案4】:

      使用控制台应用程序的一个非常简单的测试说明了在进程终止时没有调用 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()

      【讨论】:

      • 我过去也测试过这个,但我不确定这是一个全面的测试。
      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 我们以后能否访问该文件,就像它被锁定后会被解锁一样吗?
        • 是的,如果是FileStream,那么操作系统会保证当进程关闭时,底层的非托管句柄会被释放。但这并不意味着 Dispose 方法会被调用。只是操作系统知道如何在进程退出时回收文件句柄。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多