拥有Finalizer所带来的额外开销 - 在Dispose中使用/不使用SuppressFinalize

3
假设以下情况:
  • 一个类只管理成员。
  • 一些成员实现了IDisposable接口。
  • 该类是密封的 - 无法从中派生并添加非托管资源。
  • 对象在using语句内使用 - 即在完成时调用Dispose()

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

  1. 最小化的Dispose方法,它会调用IDisposable成员上的Dispose() - 没有终结器
  2. 标准的IDisposable实现,带有终结器,但Dispose()中缺少通常的GC.SuppressFinalize(this)调用。
  3. 完整的标准IDisposable实现,带有终结器(并在Dispose()中调用GC.SuppressFinalize(this))。

以下说法是否正确?我理解得对吗?

  1. 情况A.比B.和C.略微开销小,因为该对象没有终结器,所以不会进入GC的终结器队列 - 因此GC可以在收集时尽早清除该对象 - 没有开销。
  2. 情况B.该对象有终结器,因此将进入GC的终结器队列,并且将调用终结器(因为它没有被抑制) - 终结器调用Dispose,但什么也不做,因为已经被调用过了。这会产生小的开销,即对象在终结器队列中以及终结器调用的非常小的开销。
  3. 情况C.该对象具有终结器,因此仍将进入GC的终结器队列。由于已调用DisposeSuppressFinalize,因此终结器不会运行。这种情况仍然会产生对象进入终结器队列的小开销,但实际上不会运行终结器。

这里的关键点是很容易认为“通过调用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);
}

必须注明“您绝不能假定会调用终结器”的评论。另外,可能导致终结器未被调用的情况是很多的,请参阅http://blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx。 - Binary Worrier
1
这是一个愚蠢的问题,只有托管成员的类绝不能有终结器。事实上,任何C#类都不应该有终结器,这是.NET 1.x的一个错误。在2.0中,使用SafeHandle类及其关键终结器进行了优雅的修复。从那里尝试推理“可能发生什么”场景是没有用的。别这么做。 - Hans Passant
@Hans Passant:我提出这个问题是因为我的同事添加了一个终结器并声称“没有开销”,因为他调用了SuppressFinalize - 我只是想澄清一下,我认为确实存在开销。我坚定地支持您的“如果没有非托管资源,则不使用终结器”的观点 - 我只是试图公开解决与同事的讨论。 - Ricibob
3
@Ricibob也许你可以打开他正在处理的文件之一,在一个return语句后面添加一些代码页。如果他问,就说这没有任何额外开销,因为这段代码永远不会运行。 - C.Evenhuis
仅仅运行实际的终结器方法肯定不是不抑制的唯一代价。最大的终结代价是对象需要存活一个完整的额外世代,以及它所引用的所有内容。此外,还有切换到终结线程的代价,但这可能只是吹毛求疵。 :) - relatively_random
3个回答

4
不同版本的.NET GC可能会有不同处理方式,但据我的了解,任何具有Finalize方法的对象都将被添加到“终结器队列”(要求在被弃置时通知的对象列表),并且只要该对象存在,就会保留在该队列中。取消注册和重新注册终结器的方法(在我看来应该是Object的受保护成员)会设置或清除对象头中的标志,以控制是否应将该对象移动到“可自由回收队列”(finalize方法应尽快运行的对象列表),如果该对象被发现被弃置,则不会导致该对象添加或从终结器队列中删除。
因此,每个覆盖Finalize的类型的每个实例将对每个垃圾回收周期产生小但非零的开销,只要它存在。在放弃对象之前在对象上调用SuppressFinalize将防止其被移动到可自由回收队列,但不会消除由于该对象在终结器队列中存在而产生的开销。
我建议不应让任何公共对象实现Finalize。虽然存在一些合法的Finalize方法使用,但重写它的类应避免持有与终结无关的引用(令人烦恼的是,如果可终结对象仅持有对弱引用的引用,则即使弱引用的目标仍然存活,在终结运行之前,弱引用也可能无效)。

1

你只需要在需要清理非托管资源的对象上添加终结器。由于你只有托管成员,因此不需要终结器-如果它们有终结器并且未为它们调用 GC.SuppressFinalize(),则终结器将在成员本身上运行。

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

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

关于编号问题:

  1. 是的,运行终结器将导致在收集时产生一些开销。
  2. 是的,在已释放的对象上调用Dispose()应该被忽略
  3. 当创建实例时,这些类会被添加到终结队列中,但在GC尝试收集它时不会添加到自由队列中 - 将对象排队后稍后再忽略它是没有意义的。另请参见此链接

“标记为可终结,但未被添加到终结队列” - 这条消息是正确的吗?与实际加入终结队列相比,标记为可终结会带来多大的开销?标记为可终结是否会影响 GC 清理这些对象的效率? - Ricibob
我自己也感到困惑了。你是对的,它们被添加到了finalization队列;当GC出现时,对象被添加到freachable队列中,这可以防止对象被立即回收。如果调用了SuppressFinalize(),则不会将对象添加到这个第二个队列中。 - C.Evenhuis

0

我有时会使用终结器进行调试,以检查是否遗漏了某些释放操作。如果有人感兴趣,我在我的系统上进行了快速测试以检查性能影响(Windows 10、.Net 4.7.1、Intel Core i5-8250U)。

添加终结器并禁止它的成本大约为每个对象60纳秒,而添加它并忘记调用dispose的成本大约为每个对象800纳秒。性能影响在调试/发布版本和附加/不附加调试器的情况下非常一致,这可能是因为垃圾收集器在两个版本中都是相同的。

添加终结器并禁止它的性能影响很小,除非您正在构建大量这些对象,这通常不是情况。即使是微软自己的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);
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接