.NET的终结器总是被执行吗?

19

在.NET中,finalizers是否有保证会在某个时间点(避免了断电等情况)执行?我知道GC的工作原理,以及它是不确定何时运行的。

(搜索结果没有给出满意的答案,因此我在高度期望与不易发现的实际答案合并时添加了这个问题。除此之外,我已经知道答案,并将在几天后添加它,以防没有人提到它。)

2个回答

24

正如Raymond Chen解释的那样,finalizer可能实际上永远不会被执行。有趣的是这个问题在他的年度CLR周期间被提出,就在他解释之后的两天 :)

如果您想知道是否可以依赖finalizers,那么这已经是您需要了解的全部内容:不要依赖finalizers。

正确编写的程序不能假设finalizers将会执行。

正如Raymond Chen在链接的文章中所述:

Finalizers是一种安全网,而不是资源回收的主要手段。

如果您正在寻找如何释放资源,请查看可处置模式。


例如,finalizer可能不会运行,如果:

  • 另一个finalizer抛出异常。
  • 另一个finalizer花费超过2秒。
  • 所有finalizers一起花费的时间超过40秒。
  • 应用程序域崩溃或卸载(虽然可以使用关键终结器(CriticalFinalizerObject、SafeHandle或类似对象)规避此问题)
  • 没有进行垃圾回收
  • 进程崩溃

(注:时间值可能随时间变化而变化,但在一段时间内肯定是正确的。)

我猜还有很多其他原因会导致finalizer永远不会运行。除了Mr. Chen的引用之外,底线是finalizer是一个安全网,可以减少错误的影响,因为例如资源会在某个时候被释放,这比从未释放要好,如果您忘记明确释放它。


你能引用更多重要的部分吗?也许提问者正期待着那个链接。;) - mafu
实际上,由于许多人询问如何依赖终结器行为,我认为我已经引用了最重要的部分 ;) 另一方面,文章中的其他框也可能很有趣。 - OregonGhost
@OregonGhost:我理解的对吗:如果20个终结器每个需要1.95秒,那就没问题了,所有终结器都会执行,总共需要39秒。但如果有一个终结器需要2.05秒,那么其他所有终结器的执行都会被跳过。这似乎相当糟糕。为了让其他终结器在剩余的40秒超时时间内运行,而粗暴地中断一个需要超过2秒的终结器,这是一个好的功能。但在两秒后摧毁一切似乎是一个缺陷。 - supercat
@supercat:我不知道它是否完全符合你的描述,我只是阅读了我链接的文章,另一方面,该文章声称这种行为可能已经在多年中发生了改变。不要忘记,你永远不能依赖 finalizers,所以我认为这还算可以接受。你必须在某个地方做出取舍。如果你认为 2.05 秒可以接受,那么 2.10 呢?2.15?3?5?你真的不想等待 40 秒才能关闭应用程序,这只是第三级安全网(在运行所有 finalizers 和等待每个 finalizer 2 秒之后)。应用程序不应该需要 40 秒才能关闭。 - OregonGhost
@OregonGhost:我认为在终结器上设置两秒的限制可能是一件好事,如果强制执行该限制可以让其他终结器运行;也许可以随着时间的推移减少限制(例如,每个终结器在40秒限制之前获得剩余时间的5%)。至于不依赖终结器,还有什么其他机制可以在应用程序被关闭时向对象发出保存其状态的信号呢? - supercat
@supercat:当应用程序被终止时,finalizers不会运行。如果你只是想让任何人(所有对象)在正常退出时释放资源或保存它们的状态,请使用IDisposable。如果你有某种句柄,即使例如你的应用程序域崩溃,也应该释放它,请使用SafeHandle。我从来没有在我编写的任何.NET代码中编写过一个finalizer。C++析构函数在C++/CLI中也转换为IDisposable.Dispose,而不是finalizer。因为这一切,我认为CLR中使用的简单超时系统很好,尽管你的也可以。 - OregonGhost

6

如果 finalizer 抛出异常,其他 finalizer 将不会执行。

如果您在对象上调用 SuppressFinalizer,也可以禁止 finalizer。

来自 MSDN (Object.Finalize):

在以下异常情况下,Finalize 方法可能无法完成运行或根本不运行:

  • 另一个 finalizer 无限期地阻塞(进入无限循环、尝试获取它永远无法获取的锁等)。因为运行时尝试将 finalizer 完全运行,如果 finalizer 无限期地阻塞,则可能不会调用其他 finalizer。
  • 进程终止而没有给运行时清理的机会。在这种情况下,运行时的第一个进程终止通知是 DLL_PROCESS_DETACH 通知。

你能为“异常”部分添加一个来源吗? - mafu
请注意,在CLR实现中,“进入无限循环”意味着“超过两秒钟”,具体解释请参考这里:http://nitoprograms.blogspot.com/2009/08/finalizers-at-process-exit.html - OregonGhost
@mafutrct - 请看这里:http://stackoverflow.com/questions/1538630/exceptions-during-finalize-what-methodology-are-you-using-to-detect-garbage-co - Oded

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接