【问题标题】:Dispose/finalize pattern : disposing managed ressources处置/完成模式:处置托管资源
【发布时间】:2015-09-06 22:33:49
【问题描述】:

假设我有一个名为 Base 的类,它有 3 个属性:

class Base : IDisposable
{
    private string _String;
    private Class1 classe1;
    private int foo;

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

    public virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            Console.WriteLine("Free Managed ressources");
                  //HOW TO FREE _String, class1 and foo ?!          
        }
        Console.WriteLine("Free unmanaged ressources");
    }

    ~Base()
    {
        this.Dispose(false);
    }
}

还有一个名为 Class1 的类,它有 2 个属性:

class Class1
{
    public int entier { get; set; }
    public string Nom { get; set; }
}

我的问题是:如何在 Dispose 方法中释放 Base 的属性? (_String, classe1, foo)

【问题讨论】:

  • string 和 int 不实现 IDisposable 接口,因此您无需处理它们。更多信息在这里:msdn.microsoft.com/en-us/library/498928w2(v=vs.110).aspx
  • 你不能处理int和string。
  • 那么,这三个属性什么时候可以从堆中释放出来呢?垃圾收集器不会释放它们,因为我有 GC.SuppressFinalize(this)
  • 又错了。你不会在这里得到好的答案,首先阅读实际发生的事情。处理!= 完成!= 收集。
  • “这些对象什么时候会从堆中释放出来?” - 当垃圾收集器喜欢它的时候。你不应该在乎。你为什么想知道?

标签: c# garbage-collection dispose finalize


【解决方案1】:

在这种情况下,你根本不应该实现IDisposable,或者如果它存在是因为它被认为很可能在未来很有必要,那么它就会有一个空的实现。你当然不应该在那里有一个终结者。除非您 100% 确定确实需要一台,否则永远不要拥有一台。

在某些情况下,您希望实现 IDisposable,在某些情况下,您还希望拥有一个析构函数(这是 C# 拥有终结器的方式)。

其中一个是当对象完成时您有一些非常重要的事情要做,通常会撤消您之前所做的事情,例如释放您获得的句柄,关闭您打开的连接等,但不是托管内存。 (所有对象都使用托管内存,并且所有对象都为它们清理托管内存,如果它们不能再次使用并且其他东西需要更多托管内存,这就是 managed 中的“托管内存”是指)。

public class SomeClass : IDisposable
{
  private IntPtr _someHandle;
  public SomeClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
  }
  public void Dispose()
  {
    ReleaseHandle(_someHandle);
  }
}

所以现在,每当使用 SomeClass 的东西用它完成时,它就会在其上调用 Dispose()(可能通过 using 块隐式地调用)并且一切都被很好地清理了。

但如果这没有发生呢?好吧,这就是为什么我们可能会有一个终结者:

public class SomeClass : IDisposable
{
  private IntPtr _someHandle;
  public SomeClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
  }
  public void Dispose()
  {
    ReleaseHandle(_someHandle);
    _someHandle = null; // so we know not to release twice.
  }
  ~SomeClass()
  {
    if(_someHandle != null)
      ReleaseHandle(_someHandle);
  }
}

所以,如果Dispose() 没有被调用,我们仍然会进行清理,因为正常的垃圾收集过程:

  1. 意识到您需要更多内存。
  2. 查找不再使用的对象。
  3. 回收这些对象的内存。

添加了以下步骤:

  1. 实现您要回收其内存的对象有一个要运行的终结器。
  2. 将该对象放入其他此类对象的队列中。
  3. (在单独的线程上)运行对象的终结器。
  4. 该对象不再是按照上述第 4 步“需要运行终结器”的对象,因此下次可以回收它。

所有这些都有缺点:

  1. 我们无法保证何时会发生这种情况。
  2. 我们没有在第 3 步中回收尽可能多的内存,因为有这样一个对象。
  3. 垃圾收集是分代的,与对象的分代收集相得益彰意味着要么很快死去,要么活得很长,在 GC 第一次尝试收集对象之后死去几乎是最不理想的时间。

我们可以通过调用Dispose() 来绕过前两个,而不是让终结发生,这取决于类的用户,而不是类本身。我们通过让一个知道它不需要最终确定的对象来解决第三个问题,将自己标记为不再需要:

public class SomeClass : IDisposable
{
  private IntPtr _someHandle;
  public SomeClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
  }
  public void Dispose()
  {
    ReleaseHandle(_someHandle);
    GC.SuppressFinalize(this);
  }
  ~SomeClass()
  {
    ReleaseHandle(_someHandle);
  }
}

如果已将对象传递给GC.SuppressFinalize(),则不会发生第 4 步及后续步骤。

第二种情况,你可能会实现IDisposable 是你有一个IDisposable 对象作为另一个“拥有”它的对象的字段(控制它的生命周期):

public class SomeOtherClass : IDisposable
{
  private SomeClass _someObj;
  public SomeOtherClass(string someIdentifier)
  {
    _someObj = new SomeClass(someIdentifier);
  }
  public void Dispose()
  {
    //If base type is disposable
    //call `base.Dispose()` here too.
    _someObj.Dispose();
  }
}

清理SomeOtherClass 因此意味着清理它作为字段的SomeClass。请注意,我们这里没有有一个终结器。我们不需要终结器,因为它无关紧要;充其量它什么也不做,只有上面提到的终结者的缺点,更糟糕的是它会尝试清理_someObj而不知道这是否会在_someObj清理之前或之后发生,并且_someObj排队清理以一种可以假设没有其他任何东西可以进行清理的方式自行清理。

对于第三种情况,考虑我们是否将这两种情况与一个具有both它释放的非托管资源和一个一次性类的字段的类结合起来。在这里,如果我们是Dispose()d,我们想清理两者,但如果我们最终确定,我们只想清理直接处理的非托管资源:

public sealed class SomeHybridClass : IDisposable
{
  private IntPtr _someHandle;
  private SomeClass _someObj;
  public SomeHybridClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
    _someObj = new SomeClass(someIdentifier);
  }
  public void Dispose()
  {
    ReleaseHandle(_someHandle);
    GC.SuppressFinalize(this);
    _someObj.Dispose();
  }
  ~SomeHybridClass()
  {
    ReleaseHandle(_someHandle);
  }
}

现在,由于这里有重复,因此将它们重构为相同的方法是有意义的:

public sealed class SomeHybridClass : IDisposable
{
  private IntPtr _someHandle;
  private SomeClass _someObj;
  public SomeHybridClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
    _someObj = new SomeClass(someIdentifier);
  }
  private void Dispose(bool disposing)
  {
    if(disposing)
    {
      _someObj.Dispose();
    }
    ReleaseHandle(_someHandle);
  }
  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }
  ~SomeHybridClass()
  {
    Dispose(false);
  }
}

对于第四种情况,想象一下如果这个类不是密封的;它的派生类型也需要能够进行这种清理,因此我们将参数化的Dispose(bool) 方法设为保护:

public class SomeHybridClass : IDisposable
{
  private IntPtr _someHandle;
  private SomeClass _someObj;
  public SomeHybridClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
    _someObj = new SomeClass(someIdentifier);
  }
  protected virtual void Dispose(bool disposing)
  {
    // if this in turn was derived, we'd call
    // base.Dispose(disposing) here too.
    if(disposing)
    {
      _someObj.Dispose();
    }
    ReleaseHandle(_someHandle);
  }
  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }
  ~SomeHybridClass()
  {
    Dispose(false);
  }
}

但是,最后这两个示例确实解决了错误的问题:它们解决了如何让类既具有一次性类型作为字段又具有非托管资源和/或成为类型的一部分的问题发生这种情况的层次结构。真的,你最好不要陷入这种情况;要么有一个仅处理非托管资源的类(并且是sealed)或在字段中有一次性类型,你最终只能处理前两种情况。如果您通过从SafeHandle 派生来处理非托管资源,那么您实际上只需要担心第二种情况,而且它也可以管理一些困难的边缘情况。

确实,终结器应该非常、非常少地编写,并且在编写它们时应该尽可能简单,因为它们本身就有足够的复杂性以及它们周围的边缘情况。您需要知道如何处理覆盖protected virtual void Dispose(bool disposing)(注意,永远不应该公开)来处理对某人来说似乎是个好主意但没有可继承类同时具有非托管和托管一次性资源的遗留问题强迫别人进入那个位置。

如何在 Dispose 方法中释放 Base 的属性? (_String, classe1, foo)

现在应该清楚了,这些字段(属性在 .NET 中是完全不同的东西)不需要被释放。他们拥有的唯一资源是托管内存,因此一旦无法访问它们(不在静态中,不打算在方法中对它们做某事,也不在某个领域在这些类别中的任何一个或其中任何一个的字段中的某物的字段中,等等)他们的内存将在需要时自动回收。

【讨论】:

  • 谢谢你这么详细的回答!
【解决方案2】:

我的问题是:如何在 Dispose 中释放 Base 的属性 方法 ? (_String, classe1, foo)

你不需要,这是垃圾收集器的工作。实现IDisposable 是框架让您释放已分配的任何非托管 资源的一种方式,并释放实现IDisposable 的托管对象(这些对象又持有其他非托管资源)。

您的一次性工具IDisposable 上没有任何托管对象,一旦不再有任何对象指向您的Base 类,它们就会被收集。那什么时候会发生?在任意时间,当 GC 看到第 0 代不再有空间时,它需要收集。您无需执行任何操作。

实现IDisposable 并不意味着“一旦我运行这个对象就会被立即收集 Dispose()”,它仅仅意味着框架让你有机会回收它可能没有的任何资源知道(例如非托管的)。这是一种推荐的方法,如果实现了终结器,则通过GC.SuppressFinalize 抑制对其的调用,从而节省 GC 将对象从终结器队列移动到 F-Reachable 队列的麻烦,从而使其更早地可供收集.

这 3 个属性何时会从堆中释放出来?垃圾 收集器不会释放它们,因为我有 GC.SuppressFinalize(this)

您对 GC 的工作原理以及 SuppressFinalize 的含义有一个基本的误解。 GC 将在不确定的时间运行,你基本上不应该关心什么时候发生。在你之后清理是他的责任。在实现终结器的对象上调用 SuppressFinalize 只不过是在对象标头中设置一个位,运行时在调用终结器时会检查该位,这将阻止终结器运行

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多