Is GC.SuppressFinalize guaranteed?

后端 未结 5 877
清酒与你
清酒与你 2020-12-31 23:10

My observation in practice has been that GC.SuppressFinalize does not always suppress the call to the finalizer. It could be that the finalizer gets called nontheless. I won

相关标签:
5条回答
  • 2020-12-31 23:43

    I'm always using this design pattern to implement the IDisposable interface. (which is suggested by Microsoft) and for me GC.SuppressFinalize always has the nature of a guarantee!

    using System;
    using System.ComponentModel;
    
    //The following example demonstrates how to use the GC.SuppressFinalize method in a resource class to prevent the clean-up code for the object from being called twice.
    
    public class DisposeExample
    {
        // A class that implements IDisposable.
        // By implementing IDisposable, you are announcing that 
        // instances of this type allocate scarce resources.
        public class MyResource : IDisposable
        {
            // Pointer to an external unmanaged resource.
            private IntPtr handle;
            // Other managed resource this class uses.
            private readonly Component component = new Component();
            // Track whether Dispose has been called.
            private bool disposed;
    
            // The class constructor.
            public MyResource(IntPtr handle)
            {
                this.handle = handle;
            }
    
            // Implement IDisposable.
            // Do not make this method virtual.
            // A derived class should not be able to override this method.
            public void Dispose()
            {
                Dispose(true);
                // This object will be cleaned up by the Dispose method.
                // Therefore, you should call GC.SupressFinalize to
                // take this object off the finalization queue 
                // and prevent finalization code for this object
                // from executing a second time.
                GC.SuppressFinalize(this);
            }
    
            // Dispose(bool disposing) executes in two distinct scenarios.
            // If disposing equals true, the method has been called directly
            // or indirectly by a user's code. Managed and unmanaged resources
            // can be disposed.
            // If disposing equals false, the method has been called by the 
            // runtime from inside the finalizer and you should not reference 
            // other objects. Only unmanaged resources can be disposed.
            private void Dispose(bool disposing)
            {
                // Check to see if Dispose has already been called.
                if (!disposed)
                {
                    // If disposing equals true, dispose all managed 
                    // and unmanaged resources.
                    if (disposing)
                    {
                        // Dispose managed resources.
                        component.Dispose();
                    }
    
                    // Call the appropriate methods to clean up 
                    // unmanaged resources here.
                    // If disposing is false, 
                    // only the following code is executed.
                    CloseHandle(handle);
                    handle = IntPtr.Zero;
                }
                disposed = true;
            }
    
            // Use interop to call the method necessary  
            // to clean up the unmanaged resource.
            [System.Runtime.InteropServices.DllImport("Kernel32")]
            private extern static Boolean CloseHandle(IntPtr handle);
    
            // Use C# destructor syntax for finalization code.
            // This destructor will run only if the Dispose method 
            // does not get called.
            // It gives your base class the opportunity to finalize.
            // Do not provide destructors in types derived from this class.
            ~MyResource()
            {
                // Do not re-create Dispose clean-up code here.
                // Calling Dispose(false) is optimal in terms of
                // readability and maintainability.
                Dispose(false);
            }
        }
    
        public static void Main()
        {
            // Insert code here to create
            // and use a MyResource object.
        }
    }
    

    Source: MSDN: GC.SuppressFinalize Method

    0 讨论(0)
  • 2020-12-31 23:52

    I have used the exact same pattern many times and GC.SupressFinalize has always appeared to work.

    Keep in mind that a call to GC.ReRegisterForFinalize will cause objects to re-register for finalisation.

    Whenever I use the technique above I always ensure that include a full stack trace during object construction so I can track down the method that allocated the non-disposed object.

    Eg. in the constructor use

    StackFrame frame = new StackFrame(1);
    

    and report that in your debug message during the finaliser.

    Also, I notice your GC.SupressFinalize is not in a finally clause, if an exception is thrown during dispose, your objects finalizer will not be suppressed.

    0 讨论(0)
  • 2020-12-31 23:56

    One oddity you may be seeing is that the finalizer can still run even while an instance method is still running, so long as that instance method doesn't use any variables later on. So in your sample code, the Dispose method doesn't use any instance variables after the first line. The instance can then be finalized, even though Dispose is still running.

    If you insert a call to GC.KeepAlive(this) at the end of the Dispose method, you may find the problem goes away.

    Chris Brumme has a blog post about this, and I think there's another around somewhere...

    0 讨论(0)
  • 2021-01-01 00:03

    I throw an InvalidOperationException in the finalizer which makes it easy to find types which haven't been disposed properly. When Dispose() is called where GC.SuppressFinalize is invoked, I never get an exception.

    0 讨论(0)
  • 2021-01-01 00:05

    When an object with a user-defined finalizer is constructed, the runtime has to keep an internal reference to it so when it becomes unreachable in user code it can be still have the finalizer called in the runtime's finalization thread. Considering that time is of the essence when calling finalizers, it makes no sense to keep the objects in the queue if the user has requested them be suppressed. In my test CLI implementation, I keep a SuppressFinalizer flag in the header of objects that have user-defined finalizers. If the flag is true when the finalizer thread reaches that object in the queue, the finalizer call is skipped. I don't remove the object from the queue so I can keep the call to GC.SuppressFinalize() O(1) instead of O(N), where N is the number of allocated finalizable objects (I might change this policy to a deferred removal policy later).

    0 讨论(0)
提交回复
热议问题