使用Finalizer的开销 - 在Dispose中使用/不使用SuppressFinalize

时间:2015-03-19 09:12:08

标签: c# dispose idisposable finalizer suppressfinalize

假设如下:

  • 一个班级只管理过会员。
  • 部分成员实施IDisposable
  • 该类为sealed - 类无法派生并添加非托管资源。
  • 该对象在using语句中使用 - 即完成后调用Dispose()

此类有IDisposable的3种可能实现:

  1. Dispose成员上调用Dispose()的最小IDisposable方法 - 无终结器
  2. 标准IDisposable具有终结器但缺失的实施GC.SuppressFinalize(this)中通常的Dispose()来电。
  3. 使用Finalizer进行完整的标准IDisposable实施(并GC.SuppressFinalize(this)调用Dispose())。
  4. 以下陈述是否正确?我是否正确理解了这一点?

    1. 案例A的开销略低于B.和C.因为该对象没有终结器,因此它不会进入GCs终结队列 - 因为GC可以在收集的早期清理此对象 - 没有开销。
    2. 案例B.对象有一个终结器,所以最终会在GCs终结器队列中结束,终结器将被调用(因为它没有被抑制) - 终结器调用dispose,因为它已被调用,所以什么都不做。这导致对象在终结器队列中的开销很小,并且终结器调用的开销非常小。
    3. 案例C.该对象具有终结器,因此仍将在GCs终结器队列中结束。因为已经调用了dispose和SuppressFinalize,所以终结器将不会运行。这种情况仍会导致对象进入终结器队列的开销很小,但终结器实际上并没有运行。
    4. 这里的关键点是,很有可能认为“我通过调用SuppressFinalize避免了终结器开销” - 但我认为(并希望澄清)是不正确的。在终结器队列中的对象的开销仍然会发生 - 你要避免的只是实际的终结器调用 - 在通常情况下只是“我已经处理掉了”。

      注意:这里的“完全标准IDisposable实现”是指旨在涵盖非托管和托管资源案例的标准实现(请注意,此处我们只有托管对象成员)。

      public void Dispose() {
          Dispose(true);
          GC.SuppressFinalize(this);
      }
      
      private bool _disposed;
      protected virtual void Dispose(bool disposing) {
      if (_disposed)
          return;
          if (disposing) {
              // dispose managed members...
          }
          _disposed = true;
      }
      
      ~AXCProcessingInputs() {
          Dispose(false);
      }
      

3 个答案:

答案 0 :(得分:2)

不同版本的.NET GC可能会有不同的做法,但根据我的理解,任何带有Finalize方法的对象都会被添加到"终结器队列" (如果放弃请求通知的对象列表),并且只要它存在就会保留在该队列中。取消注册和重新注册以进行最终化的方法(IMHO应该是Object的受保护成员)设置或清除对象标题中的标志,该标志控制是否应将对象移动到"可释放队列&#34 ; (finalize方法应该尽快运行的对象列表)如果发现它被放弃,但不会导致在终结器队列中添加或删除该对象。

每个覆盖Finalize的类型的每个实例都会对它所参与的每个垃圾收集周期(只要它存在)施加一个很小但非零的开销。在放弃对象之前调用SuppressFinalize将阻止它被移动到可释放的队列,但是不会消除由于它在整个存在期间处于可终结队列中而产生的开销。

我建议没有面向公众的对象应该实现FinalizeFinalize方法有一些合法的用法,但覆盖它的类应该避免保留对最终化不需要的任何东西的引用(有点烦人,如果可终结对象持有对弱引用的唯一引用,弱引用可能是在终结器运行之前无效,即使弱引用的目标仍然存在)。

答案 1 :(得分:1)

您应该只在需要清理非托管资源的对象上包含终结器。由于您只有托管成员,因此您不需要终结器 - 如果终结器具有终结器并且未调用GC.SuppressFinalize(),则终结器将自动运行。

终结器的目标是在GC感觉到时清理非托管资源及其托管包装器,而Dispose模式的目标是清理上的任何类型的资源特定时刻

没有人会想到"我通过调用SuppressFinalize"来避免终结器开销。 - 相反,他们应该思考"我已经清理了我的非托管资源,不需要终结器运行"。

关于编号问题:

  1. 是的,使用终结器会在收集时产生一些开销
  2. 是的,在已经处置的对象should be ignored
  3. 上调用Dispose()
  4. 当创建实例时,这些类被添加到终结队列中,但是当GC收集它时,它们不会被添加到可释放的队列中 - 将对象排队以后稍后忽略它是没有意义的。另请参阅this link

答案 2 :(得分:0)

我有时将终结器用于调试目的,以检查是否缺少某些处置方法。如果有人感兴趣,我会在系统上进行快速测试以检查对性能的影响(Windows 10,.Net 4.7.1,Intel Core i5-8250U)。

添加终结器并抑制它的成本大约为每个对象60 ns ,添加终结器并忘记调用处置的成本大约为每个对象800 ns 。对性能的影响与调试/发行版以及有/没有附加调试器非常一致,这可能是因为两个版本中的垃圾收集器是相同的。

添加终结器并抑制终结器对性能的影响是最小的,除非要构造大量的这些对象,通常情况并非如此。甚至Microsoft自己的Task都使用终结器(几乎总是被抑制),并且该类具有非常轻的性能。因此,他们显然同意。

但是,让终结器运行可能会变得很糟糕。考虑到我的测试用例使用了一个没有引用对象的琐碎类,并且它已经慢了一个数量级。拥有大量被引用的对象应该花费更多,因为所有这些对象都需要保持生命,才能再一代。这也可能导致在垃圾回收的压缩阶段发生大量复制。

测试的源代码:

using System;
using System.Diagnostics;

namespace ConsoleExperiments
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            GenerateGarbageNondisposable();
            GenerateGarbage();
            GenerateGarbageWithFinalizers();
            GenerateGarbageFinalizing();

            var sw = new Stopwatch();

            const int garbageCount = 100_000_000;

            for (var repeats = 0; repeats < 4; ++repeats)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                sw.Restart();
                for (var i = 0; i < garbageCount; ++i)
                    GenerateGarbageNondisposable();
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                Console.WriteLine("Non-disposable: " + sw.ElapsedMilliseconds.ToString());

                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                sw.Restart();
                for (var i = 0; i < garbageCount; ++i)
                    GenerateGarbage();
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                Console.WriteLine("Without finalizers: " + sw.ElapsedMilliseconds.ToString());

                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                sw.Restart();
                for (var i = 0; i < garbageCount; ++i)
                    GenerateGarbageWithFinalizers();
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                Console.WriteLine("Suppressed: " + sw.ElapsedMilliseconds.ToString());

                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                sw.Restart();
                for (var i = 0; i < garbageCount; ++i)
                    GenerateGarbageFinalizing();
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                Console.WriteLine("Finalizing: " + sw.ElapsedMilliseconds.ToString());

                Console.WriteLine();
            }

            Console.ReadLine();
        }



        private static void GenerateGarbageNondisposable()
        {
            var bla = new NondisposableClass();
        }

        private static void GenerateGarbage()
        {
            var bla = new UnfinalizedClass();
            bla.Dispose();
        }

        private static void GenerateGarbageWithFinalizers()
        {
            var bla = new FinalizedClass();
            bla.Dispose();
        }

        private static void GenerateGarbageFinalizing()
        {
            var bla = new FinalizedClass();
        }



        private class NondisposableClass
        {
            private bool disposedValue = false;
        }

        private class UnfinalizedClass : IDisposable
        {
            private bool disposedValue = false;

            protected virtual void Dispose(bool disposing)
            {
                if (!disposedValue)
                {
                    if (disposing)
                    {
                    }

                    disposedValue = true;
                }
            }

            public void Dispose()
            {
                Dispose(true);
            }
        }

        private class FinalizedClass : IDisposable
        {
            private bool disposedValue = false;

            protected virtual void Dispose(bool disposing)
            {
                if (!disposedValue)
                {
                    if (disposing)
                    {
                    }

                    disposedValue = true;
                }
            }

            ~FinalizedClass()
            {
                Dispose(false);
            }

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