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

问题描述 投票:3回答:3

假设如下:

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

这个类有3种可能的IDisposable实现:

  1. Dispose成员上调用Dispose()的最小IDisposable方法 - 没有终结器。
  2. 使用Finalizer的标准IDisposable实现但是缺少GC.SuppressFinalize(this)中通常的Dispose()调用。
  3. 使用Finalizer的完整标准IDisposable实现(以及在GC.SuppressFinalize(this)中调用Dispose())。

以下陈述是否正确?我理解正确吗?

  1. 案例A的开销略低于B.和C.因为该对象没有终结器,因此它不会进入GCs终结队列 - 因为GC可以在收集的早期清理此对象 - 没有开销。
  2. 情况B.对象有一个终结器,因此最终会在GCs终结器队列中结束,终结器将被调用(因为它没有被抑制) - 终结器调用dispose,因为它已经被调用,所以什么都不做。这导致对象在终结器队列中的开销很小,并且终结器调用的开销非常小。
  3. 情况C.该对象具有终结器,因此仍将在GCs终结器队列中结束。因为处理并因此SuppressFinalize被称为终结者将不会运行。这种情况仍然会导致对象进入终结器队列的开销很小,但终结器实际上并没有运行。

这里的关键点是,很有可能认为“我通过调用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);
}
c# dispose idisposable finalizer suppressfinalize
3个回答
2
投票

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

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

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


1
投票

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

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

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

关于编号问题:

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

0
投票

我有时会使用终结器进行调试,以检查我是否在某个地方丢失了一些处理。如果有人有兴趣,我会对我的系统进行快速测试以检查性能影响(Windows 10,.Net 4.7.1,Intel Core i5-8250U)。

添加终结器并对其进行抑制的成本大约为每个对象60 ns,添加它和忘记调用dispose的成本大约为每个对象800 ns。性能影响与调试/发布版本非常一致,并且附加/不附带调试器,可能是因为垃圾收集器在两个版本中都是相同的。

添加终结器并抑制它的性能影响很小,除非你构建了大量的这些对象,通常情况并非如此。甚至微软自己的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);
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.