3

假设如下:

  • 一个类只管理成员。
  • 一些成员实施IDisposable
  • 该类是sealed- 一个类不能从非托管资源派生和添加。
  • 该对象在using语句中使用 - 即Dispose()在完成时调用。

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

  1. Dispose调用成员Dispose()的最小方法 -没有终结器IDisposable
  2. IDisposable带有终结器的标准实现,但缺少GC.SuppressFinalize(this).Dispose()
  3. IDisposable带有终结器(和GC.SuppressFinalize(this)调用)的完整标准实现Dispose()

以下陈述是否正确?我是否正确理解了这一点?

  1. 情况 A. 的开销比 B. 和 C. 略少,因为对象没有终结器,因此它不会进入 GC 终结队列 - 因为 GC 可以在收集的早期清理这个对象 - 没有开销。
  2. 情况 B. 对象有一个终结器,因此最终会出现在 GC 终结器队列中,并且终结器将被调用(因为它没有被抑制) - 终结器调用 dispose,因为它已经被调用,所以它什么都不做。这会导致对象在终结器队列中的小开销和终结器调用的非常小的开销。
  3. 案例 C. 对象有一个终结器,因此仍会在 GC 终结器队列中结束。因为 dispose 并因此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);
}
4

3 回答 3

3

不同版本的 .NET GC 可能会做不同的事情,但据我了解,任何具有Finalize方法的对象都将添加到“终结器队列”(如果放弃则请求通知的对象列表),并将一直保留在该队列中因为它存在。取消注册和重新注册以完成最终确定的方法(恕我直言,应该是受保护的成员Object)在对象头中设置或清除一个标志,该标志控制是否应将对象移动到“freachable queue”(其finalize方法应运行为的对象列表尽快)如果发现它被放弃,但不会导致对象被添加或从终结器队列中删除。

因此,覆盖的每种类型的每个实例都会对它参与的每个垃圾收集周期Finalize施加一个小但非零的开销,只要它存在。在放弃一个对象之前调用它会阻止它被移动到 freachable 队列中,但不会消除它在整个存在过程中一直在 finalizable 队列中所产生的开销。SuppressFinalize

我建议任何面向公众的对象都不应该实现Finalize. 方法有一些合法用途Finalize,但是覆盖它的类应该避免持有对终结不需要的任何东西的引用(有点烦人的是,如果可终结的对象持有对弱引用的唯一引用,则弱引用可能在终结器运行之前失效,即使弱引用的目标还活着)。

于 2015-03-19T16:40:44.967 回答
1

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

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

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

关于编号的问题:

  1. 是的,运行终结器会在收集时产生一些开销
  2. 是的,应该忽略Dispose()调用已经释放的对象
  3. 这些类在创建实例时添加到终结队列中,但在 GC 尝试收集它时不会添加到 freachable 队列中 - 将对象排队只是为了稍后忽略它是没有意义的。另请参阅此链接
于 2015-03-19T10:53:37.360 回答
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);
            }
        }
    }
}
于 2019-04-25T14:53:44.710 回答