【问题标题】:Throwing exception in finalizer to enforce Dispose calls:在终结器中抛出异常以强制 Dispose 调用:
【发布时间】:2016-01-16 00:38:10
【问题描述】:

这是我认为推荐的典型 IDisposable 实现:

~SomeClass() {
    Dispose(false);
}

public void Dispose() {
    GC.SuppressFinalize(this);
    Dispose(true);
}

protected virtual void Dispose(bool isDisposing) {
    if(isDisposing) {
        // Dispose managed resources that implement IDisposable.
    }
    // Release unmanaged resources.
}

现在,据我了解,终结器背后的想法是,如果我不调用 Dispose,我的非托管资源仍将被正确释放。但是,据我所知,人们普遍认为不对实现 IDisposable 的对象调用 Dispose 可能是一个错误。

是否有特别的理由不完全接受这一点而改为这样做?

~SomeClass() {
    throw new NotImplementedException("Dispose should always be called on this object.");
}

public virtual void Dispose() {
    GC.SuppressFinalize(this);

    // Dispose managed resources that implement IDisposable.

    // Release unmanaged resources.
}

【问题讨论】:

  • 如果你这样做,你会拖垮整个过程,无法处理异常。为什么不改用Debug.Fail 语句?
  • 我不想处理异常。不调用 Dispose 应该是立即修复的大问题。
  • 我很想将其关闭为基于意见:我预计在意外失败时终止时大约有 50/50 的拆分与对您的库的用户很好,即使他们未能正确使用 API .
  • 我真的很想知道是否有任何我不知道的意见?
  • 考虑 Debug.Assert 而不是异常。结合一些强制 GC 的方法,它将为您提供相对可靠的方法来在调试时触发问题并在发布版本中保持良好的行为。

标签: c# .net dispose idisposable


【解决方案1】:

从 .NET 2.0 及更高版本开始,如果未覆盖默认策略,则在终结器 causes the process to terminate 中引发未处理的异常。

据我了解,Finalizer 不是应该抛出异常的预期位置。我认为Dispose() 方法可能由于意外原因(线程中止,...)而未被调用,只要终结器正确执行,仍然可以从中进行干净恢复。

【讨论】:

  • 调试器至少不会停止吗?
  • 好的。如果这是可取的呢?如果有问题,这会引起人们的注意,而不是把它扫到地毯下。看起来很合适。
  • 我当然不希望它继续工作,没有人意识到它已经坏了。
  • @Kelsie,不要依赖终结器来告诉你有什么问题,终结器不能保证被执行。
  • 终结器当然不能保证在任何特定时间执行,但是您是说在应用程序的生命周期中,它可能根本不执行吗?
【解决方案2】:

在终结器中抛出异常几乎总是一个坏主意。

即使在某些情况下它实际上会达到预期的结果(导致程序员被通知问题而不会搞砸其他任何事情),终结器无法知道什么时候会发生案例

更好的方法是使用一种方法来指示(可能通过抛出任何异常)是否有任何对象被错误地丢弃;可以在程序结束时调用该方法,或者在创建新对象时调用该方法。这可能会导致在与被放弃的特定实例无关的部分代码中引发异常,但这仍然可能比在终结器上下文中引发异常更好。

【讨论】:

    【解决方案3】:

    从终结器中抛出异常是您做过的最糟糕的事情之一。它可能导致几种不同的行为。该异常可能会导致 ExecutionEngine 崩溃,从而降低整个进程,您可能会阻止终结器导致 OutOfMemory (OOM) 崩溃等。不要忘记终结器在单个线程上运行并且是最重要的线程之一- 你不希望它搞砸了。

    【讨论】:

    • 如果这是你真正想要的怎么办?
    • @OlivierGiniaux 为什么要在运行应用程序的框架的运行时中导致未定义行为?
    • 因为您可能更喜欢具有明确问题的崩溃,您可以轻松解决内存泄漏问题,而内存泄漏会在数月或数年内默默地破坏您的应用程序。
    • @OlivierGiniaux 您将如何诊断,更不用说“轻松修复”导致框架中未定义行为的问题了?它可能会崩溃,它可能不会崩溃,它可能需要 4 周才能崩溃,它可能会破坏应用程序无关部分的内存等。关键是你不知道它会如何表现。
    【解决方案4】:

    由于开发人员倾向于变得懒惰并忽略任何可以忽略的警告,而且 IIS7.5 应用程序默认完全忽略 Debug.Fail 我也使用相同的模式。除了一个例外:

    我只在调试版本中这样做,因为它对发布版本没有任何好处。此外,它从不直接保存非托管资源的类中删除了慢速终结器。

    public void Dispose()
    {
        // Do your dispose stuff here
    #if DEBUG
        GC.SuppressFinalize(this);
    }
    ~MyClass()
    {   throw new ObjectNotDisposedException(); // This class is not intended to be used without Dispose.
    #endif
    }
    

    在某些情况下,丢失的 dispose 可能会真正占用应用程序,例如远程事务上下文。在这种情况下,终止并没有什么更糟的,而且会让懒惰的开发人员感到紧张。我总是喜欢把问题带回源头。

    【讨论】:

    • 即使有人想确保在对象被遗弃时收到通知,我认为跟踪哪些对象未完成并在应用程序关闭时显示警告(如果有任何剩余)将是一种更清洁的方法去做吧。
    • @supercat 在您看来,如果跟踪 resurrected 被遗弃的对象(如~MyClass() { StaticClass.GlobalListOfObjectsAbandoned.Add(this); Dispose(false); })可以吗?
    • @JeppeStigNielsen:打算用这份名单做什么?如果有人打算序列化并记录有关其中对象的一些信息,我认为最好收集序列化的表格。另请注意,终结器在未知的线程上下文中运行,因此应该使用某种形式的线程安全集合,例如 ConcurrentBag<Object>ConcurrentBag<String> 而不是 List<Object>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-17
    • 1970-01-01
    • 1970-01-01
    • 2017-02-10
    • 1970-01-01
    • 1970-01-01
    • 2016-12-15
    相关资源
    最近更新 更多