【问题标题】:Costs involved with C# destructors (aka: finalizers)?C# 析构函数(又名:终结器)所涉及的成本?
【发布时间】:2010-10-11 10:16:27
【问题描述】:

析构函数应该只释放你的对象持有的非托管资源,它不应该引用其他对象。如果您只有托管引用,则不需要(也不应该)实现析构函数。您只希望它用于处理非托管资源。 因为使用析构函数需要一些成本,所以您应该只在消耗宝贵的非托管资源的方法上实现它。

-- Top Ten Traps in C# for C++ Programmers

本文没有对此进行更深入的讨论,但是在 C# 中使用析构函数会涉及哪些类型的成本?

注意:我知道 GC 和析构函数不会在可靠时间被调用的事实,除此之外,还有什么其他的吗?

【问题讨论】:

    标签: c# idisposable destructor finalizer


    【解决方案1】:

    任何具有终结器的对象(我更喜欢这个术语而不是析构函数,以强调与 C++ 析构函数的区别)被添加到终结器队列中。这是对具有终结器的对象的引用列表,在删除它们之前必须调用该终结器。

    当对象准备好进行垃圾回收时,GC 会发现它在终结器队列中,并将引用移动到可访问(f-reachable)队列。这是finalizer后台线程依次调用每个对象的finalizer方法所经过的列表。

    一旦对象的终结器被调用,该对象就不再在终结器队列中,所以它只是一个常规的托管对象,GC 可以删除。

    这一切都意味着,如果一个对象有一个终结器,它会在被移除之前至少在一次垃圾回收中存活下来。这通常意味着对象将被移动到下一个堆代,这实际上涉及将内存中的数据从一个堆移动到另一个堆。

    【讨论】:

    • 我预计我的大部分对象只会在应用程序生命周期结束时被拉下和释放,这对此有何影响? GC 会一直运行直到它清除所有内容,不是吗?
    • 当应用程序结束时,GC 将允许终结器运行一些时间,但最终它只会拆除堆,即使所有对象都没有终结。如果你需要一些清理代码来运行,你应该实现 IDisposable 接口,它可以让你控制对象的生命周期。
    • 只是强调一下,当应用程序关闭时,.NET 只允许终结器运行一段固定的时间。 (我认为目前的值是每个终结器最多 10 秒,all 终结器最多 30 秒。这意味着不能保证所有终结器都能运行)
    【解决方案2】:

    The most extensive discussion I've seen on how this all works was done by Joe Duffy。它比您想象的要详细。

    随后,我将a practical approach 放在一起,每天都在做这件事 - 更少关注成本,更多关注实施。

    【讨论】:

    • 如果我来自任何地方,我来自 C,虽然我在 C# 方面比我更精通,但我只是在搜索有关信息的时候发现了这篇文章C# 中的析构函数。
    • 实际上,C# 3 语言规范仍然将它们称为析构函数,因此即使它们很硬,也与 C++ 对应物完全不同,但遗憾的是它们仍然使用相同的名称。
    • 很少有类应该有终结器。事实上,除了极少数例外,只有主要目的围绕完成的类才应该拥有它们。需要终结的非托管资源(职责)不应由拥有其他事物的类持有;每个都应该被移动到主要目的是持有它并确保它的清理(确保它的职责得到执行)的类中。由于可终结对象直接或间接引用的所有内容都必须保留到终结器运行,因此可终结类应尽可能轻量级。
    【解决方案3】:

    Guffa 和 JaredPar 很好地涵盖了细节,所以我将添加一些关于终结器或析构器的有点深奥的注释,不幸的是 C# 语言规范称之为它们。

    要记住的一点是,由于终结器线程按顺序运行所有终结器,因此终结器中的死锁将阻止所有剩余(和未来)终结器运行。由于这些实例在它们的终结器完成之前不会被收集,因此死锁的终结器也会导致内存泄漏。

    【讨论】:

      【解决方案4】:

      Guffa 已经很好地总结了终结器成本的因素。最近有一个 article 关于 Java 中终结器的成本,这也提供了一些见解。

      可以通过使用 GC.SuppressFinalize 从终结器队列中删除对象来避免 .net 中的部分成本。我根据文章在 .net 中进行了一些快速测试并将其发布到here(尽管重点更多地放在 Java 方面)。


      下面是结果图表 - 它实际上并没有最好的标签 ;-)。 "Debug=true/false" 指的是空的 vs 简单的终结器:

      ~ConditionalFinalizer()  
      {  
          if (DEBUG)  
          {  
              if (!resourceClosed)  
              {  
                  Console.Error.WriteLine("Object not disposed");  
              }  
              resourceClosed = true;  
          }  
      } 
      

      “Suppress=true”是指在 Dipose 方法中是否调用了 GC.SuppressFinalize。

      总结

      对于 .net,通过调用 GC.SuppressFinalize 从终结器队列中删除对象是将对象留在队列中的一半成本。

      【讨论】:

        猜你喜欢
        • 2017-07-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-21
        • 1970-01-01
        • 2021-01-12
        • 2011-05-17
        • 2010-12-24
        相关资源
        最近更新 更多