C# 中的“处置模式”:为什么我们需要“if(处置)”条件?

A `dispose pattern` in C#: why do we need `if (disposing)` condition?

因此,默认的 dispose 模式 实现如下所示:

class SomeClass : IDisposable
{
   // Flag: Has Dispose already been called?
   bool disposed = false;

   // Public implementation of Dispose pattern callable by consumers.
   public void Dispose()
   { 
      Dispose(true);
      GC.SuppressFinalize(this);           
   }

   // Protected implementation of Dispose pattern.
   protected virtual void Dispose(bool disposing)
   {
      if (disposed)
         return; 

      if (disposing) {
         // Free any other managed objects here.
      }

      // Free any unmanaged objects here.
      disposed = true;
   }

   ~SomeClass()
   {
      Dispose(false);
   }
}

据说:

If the method call comes from a finalizer (that is, if disposing is false), only the code that frees unmanaged resources executes. Because the order in which the garbage collector destroys managed objects during finalization is not defined, calling this Dispose overload with a value of false prevents the finalizer from trying to release managed resources that may have already been reclaimed.

问题是:为什么SomeClass的对象引用的对象可能已经被释放了,我们不应该尝试当从终结器调用方法时处理它们?如果那些对象仍然被我们的 SomeClass 对象引用,它们就不能被释放,不是吗?据说:

Those with pending (unrun) finalizers are kept alive (for now) and are put onto a special queue. [...] Prior to each object’s finalizer running, it’s still very much alive — that queue acts as a root object.

因此,我们的 SomeClass 对象再次被该队列引用(这与被根引用相同)。 SomeClass 对象引用的其他对象也应该存在(因为它们是通过 SomeClass 对象建立的)。 那么,为什么以及如何在调用 SomeClass 终结器时释放它们?

存在 .NET 中的对象,同时存在对它们的任何引用。一旦最后一个引用不存在,它们就会不复存在。当对象存在时,对象使用的存储永远不会被回收,但是 GC 在回收存储之前会做几件事:

  1. 有一个特殊的列表,称为 "finalizer queue",它包含对所有已注册终结器的对象的引用。在识别出 Universe 中任何地方存在的所有其他引用之后,GC 将检查终结器队列中的所有对象,以查看是否找到了对它们的任何引用。如果此过程导致它找到以前未发现的对象,它会将引用复制到另一个名为 "freachable queue" 的列表。任何时候 freachable 队列非空并且没有终结器 运行ning 时,系统将从该队列中提取一个引用并在其上调用终结器。

  2. GC 还将检查所有弱引用的目标,并使任何其目标未被任何有效强引用识别的弱引用无效。

请注意,finalize 方法不会 "garbage-collect" 对象。相反,它会延长对象的存在时间,直到 finalize 被调用为止, 目的是允许它履行对外部实体可能具有的任何义务。如果那时在宇宙中任何地方都不存在对该对象的引用,则该对象将不复存在。

请注意,两个具有终结器的对象可以保存对彼此的引用。在这种情况下,它们的终结器 运行 的顺序是未指定的。

Konrad Kokosa 在他的书中有一个令人印象深刻的解释 Pro .NET Memory Management。 (强调)

During GC, at the end of Mark phase, GC checks the finalization queue to see if any of the finalizable objects are dead. If they are some, they cannot be yet delete because their finalizers will need to be executed. Hence, such object is moved to yet another queue called fReachable queue. Its name comes from the fact that it represents finalization reachable objects - the ones that are now reachable only because of finalization. If there are any such objects found, GC indicates to the dedicated finalizer thread there’s work to do.

Finalization thread is yet another thread created by the.NET runtime. It removes objects from the fReachable queue one by one and calls their finalizers. This happens after GC resumes managed threads because finalizer code may need to allocate objects. Since the only root to this object is removed from the fReachable queue, the next GC that condemns the generation this object is in will find it to be unreachable and reclaim it.

Moreover, fReachable queue is treated as a root considered during Mark phase because the finalizer thread may not be fast enough to process all objects from it between GCs. This exposes the finalizable objects more to a Mid-life crisis - they may stay in fReachable queue for a while consuming generation 2 just because of pending finalization.

我认为这里的关键是:

fReachable queue is treated as a root considered during Mark phase because the finalizer thread may not be fast enough to process all objects from it between GCs.