C# 在终结器中调用GC.SuppressFinalize无害吗?

C# 在终结器中调用GC.SuppressFinalize无害吗?,c#,garbage-collection,finalizer,C#,Garbage Collection,Finalizer,因为finalizer/IDisposable和所谓的“IDisposablepattern”主题往往会引出很多姿态、武断和好战的观点(不是分别、甚至更多),我真的很犹豫问这个问题。为了避免这些老掉牙的争论,我只回答了一个非常简单的问题,这个问题似乎对StackOverflow没有一个简洁的答案 一旦对象的终结器开始执行,调用GC.SuppressFinalize(this)是否为空?更具体地说(当然),从终结器本身调用GC.SuppressFinalize(this)是否无害?(再说一遍,我们

因为
finalizer/IDisposable
和所谓的“
IDisposable
pattern”主题往往会引出很多姿态、武断和好战的观点(不是分别、甚至更多),我真的很犹豫问这个问题。为了避免这些老掉牙的争论,我只回答了一个非常简单的问题,这个问题似乎对StackOverflow没有一个简洁的答案

一旦对象的终结器开始执行,调用
GC.SuppressFinalize(this)
是否为空?更具体地说(当然),从终结器本身调用
GC.SuppressFinalize(this)
是否无害?(再说一遍,我们这里没有讨论任何“为什么”)


因此,换句话说,除了调用API及其在对象头中适当设置标志的开销之外,是否存在任何不好的、不需要的或其他明显的正确性或性能影响?

简而言之,很少使用终结器,您应该果断地清理应用程序。此外,还有种族条件以及.net中的定稿存在问题的其他原因{在此处插入大量的博客文章和一长串建议}


当您确实需要终结器时,除了
Dispose
,您还需要它,而不是
Dispose

在大多数用例中,如果要使用终结器,可以在Dispose方法中调用
SuppressFinalize
,但要回答问题(以及其他问题)

一旦对象的 终结器已开始执行

调用
SuppressFinalize
实际上非常简单,它只是在对象中设置一个标志,以防止将其添加到finalize队列中。它本身的开销很小,可以在任何代码路径中设置。这可能不是你的问题

然而,这里真正的问题是阻止GC同时完成和处理对象,以及在执行过程中对系统状态的假设

为了尝试缓解这种情况,您应该尽快调用
GC.SuppressFinalize(this)
,使用一个标志来确定您是否已处理。甚至检查appdomain或环境是否已开始卸载或关闭

if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload())

无论哪种方式,这里仍然存在竞争条件和问题,缓解或处理这些问题将取决于您需要终结器的确切原因、您正试图清理的内容以及清理方式。

当然,最好完全避免终结器,并使用
SafeHandle
,正如现代习语所示。然后所有关于终结器的东西都变得毫无意义了

尽管这样做很明智,但是从终结器调用
GC.SuppressFinalize()
是完全安全的。描述该方法的作用:

此方法在obj的对象头中设置一个位,运行时在调用终结器时检查该位

运行时实际上也可能在GC操作期间检查该位,也就是说,当发现一个对象不可访问时,该对象的终结器将被放入终结器队列。如果设置在该点,终结器甚至不会出现在队列中

稍后在调用终结器之前再次检查它,也可以避免对象的终结,如果结果是其他对象的终结器最终处理了它,即使该对象的终结器已放入终结队列

这两个检查都发生在调用终结器之前。一旦调用终结器,对象中的位就没有意义了。设置它是无害的,但不会有任何效果

顺便说一句:请注意,过去的.NET实现使用了
Finalizer
FReachable
队列。创建对象时,如果该对象具有终结器,则会将其移动到
终结器
队列。一旦无法访问该对象,它将被移动到
FReachable
队列,以便稍后完成。调用
SuppressFinalize()
将从
Finalizer
队列中删除该对象。当终结器运行时,对象不再在此队列中,因此
SuppressFinalize()
调用将是一个NOP,同样无害

现在,也就是说,您的问题很广泛:“……是否存在任何不好的、不需要的或其他有形的正确性或性能影响?”。大部分都在旁观者的眼中。我认为调用
GC.SuppressFinalize()
的终结器是不正确的。所以,这对我来说是一个“有形的正确性效应”。我还发现,偏离已发布、公认的标准模式的代码是“不需要的”。如果问题中没有更具体的标准来约束它,那么问题这一部分的答案可能是“是”、“不是”、“有时”等等

事实上,你有一个重复的问题,但没有人愿意回答它:。不过,我确实找到了关于这一点的评论线索,特别是Eric Lippert的贡献:

您的假设是,不必要的SuppressFinalize调用是您计划中的错误。这不是问题所在;问题是终结器线程上托管资源的处置。回想一下,终结器在自己的线程上运行,托管资源可以被线程亲缘化,现在开始想象可能导致的恐怖。此外:终结器以任意顺序运行。在终结器线程上部署的托管对象可能已被终结;现在您可能在一个对象上运行两次终结逻辑;它对这种情况是否稳健Eric Lippert 2016年3月31日21:58 一,

编写一个正确的终结器是非常困难的,我建议您不要尝试,理想情况下,但一定要等到您更好地理解模式后再尝试。如果你还不够害怕的话,我的关于潜艇的系列文章