C# 在Dispose中使用/不使用SuppressFinalize的终结器的开销

C# 在Dispose中使用/不使用SuppressFinalize的终结器的开销,c#,dispose,idisposable,finalizer,suppressfinalize,C#,Dispose,Idisposable,Finalizer,Suppressfinalize,假设: 类只管理成员 一些成员实现了IDisposable 类是密封的-类不能从非托管资源派生和添加非托管资源 对象在语句中使用,即Dispose()完成后调用 对于此类,IDisposable有3种可能的实现: 调用IDisposable成员上的Dispose()的最小Dispose方法-无终结器 标准的IDisposable带有终结器的实现,但是缺少了通常的GC.SuppressFinalize(this)调用Dispose() 使用Finalizer(以及使用GC.SuppressFi

假设:

  • 类只管理成员
  • 一些成员实现了
    IDisposable
  • 类是密封的-类不能从非托管资源派生和添加非托管资源
  • 对象在
    语句中使用,即
    Dispose()
    完成后调用
对于此类,
IDisposable
有3种可能的实现:

  • 调用
    IDisposable
    成员上的
    Dispose()
    的最小
    Dispose
    方法-无终结器
  • 标准的
    IDisposable
    带有终结器的实现,但是缺少了通常的
    GC.SuppressFinalize(this)
    调用
    Dispose()
  • 使用Finalizer(以及使用
    GC.SuppressFinalize(this)
    调用
    Dispose()
    )的完整标准
    IDisposable
    实现
  • 以下陈述是否正确?我理解正确了吗?

  • 案例A.is的开销略低于B.和C.因为对象没有终结器,所以它不会进入GCs终结队列-因为GC可以在收集的早期清理此对象-没有开销
  • 案例B.对象有一个终结器,因此将在GCs终结器队列中结束,终结器将得到调用(因为它未被抑制)-终结器调用dispose,而dispose不做任何操作,因为它已被调用。这会导致终结器队列中的对象开销很小,终结器调用的开销也很小
  • 案例C.该对象有一个终结器,因此仍将在GCs终结器队列中结束。由于已调用dispose,因此不会运行终结器。这种情况仍然会导致终结器队列中的对象产生较小的开销,但终结器实际上并没有运行
  • 这里的关键点是,人们很容易认为“我通过调用
    SuppressFinalize
    ,避免了终结器开销”——但我认为(,并想澄清一下)这是不正确的。终结器队列中的对象的开销仍然会发生-您所要避免的只是实际的终结器调用-在常见情况下,这只是“我已被处置,不做任何事情”

    注意:这里所说的“完整标准
    IDisposable
    implementation”是指设计用于涵盖非托管和托管资源情况的标准实现(注意,这里我们只有托管对象成员)


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

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

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

    关于编号问题:

  • 是的,在收集终结器时运行终结器将导致一些开销
  • 是,对已处理的对象调用
    Dispose()
  • 这些类在创建实例时添加到终结队列中,但在GC试图收集它时不会添加到freachable队列中——将对象排队只是在以后忽略它是没有意义的。另见

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

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


    我建议,任何面向公众的对象都不应该实现
    Finalize
    Finalize
    方法有一些合法的用途,但是重写它的类应该避免保留对Finalize不需要的任何东西的引用(有点令人烦恼的是,如果可终结对象持有对弱引用的唯一引用,则在终结器运行之前,弱引用可能会失效,即使弱引用的目标仍处于活动状态).

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

    添加终结器并抑制它的成本约为每个对象60 ns,添加终结器并忘记调用dispose的成本约为每个对象800 ns
    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);
    }
    
    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);
                }
            }
        }
    }