关于C#Dispose Pattern的具体问题

时间:2010-09-27 14:53:46

标签: c# dispose idisposable

我对C#中的Dispose模式有几个基本问​​题。

在下面的代码片段中,这似乎是实现dispose模式的标准方法,您会注意到如果disposing为false,则不会处理托管资源。他们如何/何时处理? GC是否会出现并在以后处理托管资源?但如果是这样的话,GG.SuppressFinalize(this)调用会做什么?有人能给我一个处理托管资源的例子吗?想到了解开事件的想法。还要别的吗?编写模式的方式,如果你在“if(disposing)”部分没有做任何事情,它们似乎会被处理掉(稍后)。评论

protected virtual void Dispose(bool disposing)
{  
    if (!disposed)
    {
        if (disposing)
        {
            // Dispose managed resources.
        }

        // There are no unmanaged resources to release, but
        // if we add them, they need to be released here.
    }
    disposed = true;

    // If it is available, make the call to the
    // base class's Dispose(Boolean) method
    base.Dispose(disposing);
}
// implements IDisposable
public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

我在这个帖子How do I implement the dispose pattern in c# when wrapping an Interop COM Object?中读到Dispose(bool)中的锁是否正确?它说,“Meta-meta评论 - 以及在你的非管理清理期间你永远不会获得锁或使用锁定是很重要的。”为什么会这样?它是否也适用于非托管资源?

最后,在没有实现IDisposable的情况下,是否实现了终结器(C#中的~MyClass())?我相信如果没有非托管资源,我会在某处读到终结器和IDisposable不是必需的(或者是可取的)。但是,我确实看到在一些示例中使用了没有IDisposable的终结器(参见:http://www.codeproject.com/KB/cs/idisposable.aspx作为一个示例) 谢谢, 戴夫

7 个答案:

答案 0 :(得分:6)

这种实现IDisposable模式的方式是一种故障安全方式:如果客户端忘记调用Dispose,运行时调用的终结器将稍后调用Dispose(false)(注意您的样本中缺少此部分。)

在后一种情况下,即当终结器调用Dispose时,已经清理了托管资源,否则有问题的对象将无法进行垃圾回收。

  

但如果是这样的话,GC.SuppressFinalize(this)调用会做什么?

运行终结器会产生额外费用。因此,如果可能,应该避免。调用GC.SuppressFinalize(this)将跳过运行终结器,因此可以更有效地对对象进行垃圾收集。

一般情况下,应避免依赖终结器,因为无法保证终结器能够运行。 Raymond Chen在以下文章中描述了终结器的一些问题:

  

When do I need to use GC.KeepAlive?

答案 1 :(得分:6)

没有人得到最后两个问题(顺便说一下:每个帖子只询问一个)。在Dispose()中使用锁定对终结器线程非常致命。锁定可能持有的时间没有上限,当CLR注意到终结器线程卡住时,程序将在两秒钟后崩溃。而且,这只是一个bug。当另一个线程可能仍然具有对该对象的引用时,您永远不应该调用Dispose()。

是的,在没有实现IDisposable的情况下实现终结器并非闻所未闻。任何COM对象包装器(RCW)都可以。 Thread类也是如此。这样做是因为调用Dispose()是不切实际的。对于COM包装器,因为无法跟踪所有引用计数。在Thread的情况下,因为必须Join()线程,以便你可以调用Dispose()失败了拥有一个线程的目的。

关注Jon Hanna的帖子。 99.99%的时间实现自己的终结器确实是错误的。你有SafeHandle类来包装非托管资源。你需要一些非常模糊不清的东西。

答案 2 :(得分:4)

上述模式是一个雄辩地解决处置和最终确定的重叠问题的问题。

当我们处置时,我们希望:

  1. 处置所有一次性成员物品。
  2. 处理基础对象。
  3. 释放非托管资源。
  4. 最终确定时我们想:

    1. 释放非托管资源。
    2. 除此之外还有以下问题:

      1. 处置应安全多次通话。致电x.Dispose();x.Dispose();
      2. 不应该是错误的
      3. 终结会增加垃圾回收的负担。如果我们能够避免它,特别是如果我们已经释放了非托管资源,我们希望抑制最终化,因为它不再需要。
      4. 访问已完成的对象是充满希望的。如果一个对象正在最终确定,那么任何可终结的成员(也将处理与我们的类相同的问题)可能已经或可能尚未最终确定,并且肯定会在最终确定队列中。由于这些对象可能也会被管理一次性对象,并且处理它们会释放它们的非托管资源,我们不希望在这种情况下处理它们。
      5. 您提供的代码(一旦您添加调用Dispose(false)的终结器管理这些问题。在Dispose()被调用的情况下,它将清理托管和非托管成员并禁止最终确定,同时还要防止多次调用(但在这方面并不是线程安全的。)在调用终结器的情况下,它将清理未管理的成员。

        但是,这种模式只需要在同一类中组合托管和非托管问题的反模式。更好的方法是通过仅关注该资源的类来处理所有非托管资源,无论是SafeHandle还是您自己的单独类。然后你会有两种模式中的一种,后者很少见:

        public class HasManagedMembers : IDisposable
        {
           /* more stuff here */
           public void Dispose()
           {
              //if really necessary, block multiple calls by storing a boolean, but generally this won't be needed.
              someMember.Dispose(); /*etc.*/
           }
        }
        

        这没有终结者,也不需要终结者。

        public class HasUnmanagedResource : IDisposable
        {
          IntPtr _someRawHandle;
          /* real code using _someRawHandle*/
          private void CleanUp()
          {
             /* code to clean up the handle */
          }
          public void Dispose()
          {
             CleanUp();
             GC.SuppressFinalize(this);
          }
          ~HasUnmanagedResource()
          {
             CleanUp();
          }
        }
        

        这个版本将会更加罕见(在大多数项目中都不会发生)只处理单独的非托管资源,该类是一个包装器,并且如果处理的话,终结者也会这样做。发生了。

        由于SafeHandle允许为您处理第二个模式,因此您根本不需要它。无论如何,我给出的第一个例子将处理你需要实现IDisposable的绝大多数情况。示例中给出的模式只应用于向后兼容,例如从使用它的类派生时。

答案 3 :(得分:2)

  

...如果处置错误,您将注意到托管资源未得到处理。他们是如何/何时处理的?

您没有将它包含在您的示例中,但通常该类型将具有将调用Dispose(false)的析构函数。因此,当disposingfalse时,您“知道”您正在进行终结器调用,因此应该 *不*访问任何托管资源,因为他们可能已经已经完成。

GC完成过程仅确保调用终结器,而不是调用它们的 order

  

GG.SuppressFinalize(this)调用是做什么的?

它阻止GC将您的对象添加到终结队列并最终调用object.Finalize()(即您的析构函数)。这是性能优化,仅此而已。

  

模式的编写方式,如果你在“if(disposing)”部分没有做任何事情,它们似乎会被处理掉(稍后)

也许;这取决于类型的编写方式。 IDisposable成语的一个主要观点是“确定性终结” - 说“我希望你的资源现在释放”并让它具有意义。如果您“忽略”disposing=true阻止而不“转发”Dispose()来电,则会发生以下两种情况之一:

  1. 如果类型有终结器,则最终可能会在“稍后”某个时间调用该对象的终结器。
  2. 如果类型没有终结器,则托管资源将“泄漏”,因为永远不会调用Dispose()
  3.   

    重要的是,在非管理清理期间永远不要获取锁或使用锁定。“为什么会这样?它是否也适用于非托管资源?

    这是一个理智的问题 - 你的理智。清理代码越简单越好,总是 使用锁可能会导致异常或死锁,这两种情况都是破坏一天的好方法。 : - )

      

    总是在没有实现IDisposable的情况下实现终结器(在C#中的~MyClass())

    有人可以,但这会被认为是糟糕的风格。

答案 4 :(得分:1)

处理对象的常规方法是调用它的Dispose()方法。以这种方式完成后,SuppressFinalize调用将对象从终结器队列中删除,将其转换为可以轻松进行垃圾回收的常规托管对象。

仅在代码无法正确处置对象时使用终结器。然后终结器调用Dispose(false),以便该对象至少可以尝试清理非托管资源。由于对象引用的任何托管对象在此阶段可能已经被垃圾收集,因此该对象不应该尝试清理它们。

答案 5 :(得分:0)

您可能不想尝试通过模式了解处置,而是尝试了解基于CLR基础和IDisposable接口的预期用法,以此方式实现模式的原因。有一个非常好的介绍,应该在http://msdn.microsoft.com/en-us/magazine/cc163392.aspx回答你的所有问题(以及一些你没想过的问题)。

答案 6 :(得分:0)

如果您的类将直接保存非托管资源,或者它可能会由后代类继承,那么Microsoft的dispose模式将提供一种将终结器和处理器连接在一起的好方法。如果您的类或其后代无法直接保存非托管资源,则应该删除模板代码并直接实现Dispose。鉴于Microsoft强烈建议将非托管资源包装在其唯一目的是保存它们(*)的类中(并且为了这个目的而具有类似SafeHandle的类),实际上不再需要模板代码。

(*).net中的垃圾收集是一个多步骤的过程;首先,系统确定哪些对象不在任何地方被引用;然后它会生成一个Finalize'able对象的列表,这些对象在任何地方都没有引用。该列表及其上的所有对象将被重新声明为“实时”,这意味着它们引用的所有对象也将是实时的。此时,系统将执行实际的垃圾收集;然后它将运行列表中的所有终结器。如果一个物体成立,例如字体资源的直接句柄(非托管)以及对其他十个对象的引用,这些对象又可以直接或间接引用一百多个对象,然后由于非托管资源,对象将需要一个终结器。当对象到期收集时,它所持有的直接或间接引用的100+对象都没有资格进行收集,直到其终结符运行后的传递为止。

如果不是持有字体资源的直接句柄,而是对象持有对保存字体资源的对象的引用(没有别的),后一个对象需要一个终结器,但前者不会(因为它不包含对非托管资源的直接引用。只有一个对象(持有终结器的对象)而不是100+,必须在第一次垃圾回收中存活。