Гарантируется ли GC.SuppressFinalize? - PullRequest
6 голосов
/ 27 апреля 2009

На практике я наблюдал, что GC.SuppressFinalize не всегда подавляет вызов финализатора. Может случиться так, что финализатор будет вызван тем не менее. Поэтому мне интересно, имеет ли GC.SuppressFinalize характер запроса , а не гарантии системы?


Дополнительная информация

Следующая информация может помочь предоставить больше контекста для квеста, если это необходимо.

В сводке документа GC.SuppressFinalize указано, что это запрос:

Запрашивает, чтобы система не вызывала финализатор для указанного объекта.

Интересно, было ли это случайное использование этого слова или действительно предназначено для описания поведения во время выполнения.

Я наблюдал это со следующим классом SingletonScope, взятым из проекта Schnell , который был основан на оригинальной идее Иана Гриффитса , за исключением того, что он более обобщенный. Идея состоит в том, чтобы в отладочных сборках определить, был ли вызван метод Dispose или нет. Если нет, финализатор в конце концов включится, и можно вывести предупреждение. Если вызывается Dispose, то GC.SuppressFinalize должен предотвратить запуск финализатора. К сожалению, предупреждения, похоже, срабатывают в любом случае, но не детерминированным способом. То есть они не стреляют при каждом запуске.

#region License, Terms and Author(s)
//
// Schnell - Wiki widgets
// Copyright (c) 2007 Atif Aziz. All rights reserved.
//
//  Author(s):
//      Atif Aziz, http://www.raboof.com
//
// This library is free software; you can redistribute it and/or modify it 
// under the terms of the GNU Lesser General Public License as published by 
// the Free Software Foundation; either version 2.1 of the License, or (at 
// your option) any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
// License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, 
// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

namespace WikiPad
{
    #region Imports

    using System;
    using System.Diagnostics;

    #endregion

    //
    // NOTE: To use SingletonScope and ISingletonScopeHelper with value 
    // types, use Nullable<T>. For example, if the type of value to scope
    // is ThreadPriority then use ISingletonScopeHelper<ThreadPriority?>
    // and SingletonScope<ThreadPriority?>.
    //

    //
    // In debug builds, this type is defined as a class so a finalizer
    // can be used to detect an undisposed scope.
    //

    /// <summary>
    /// Designed to change a singleton and scope that change. After exiting
    /// the scope, the singleton is restored to its value prior to entering
    /// the scope.
    /// </summary>

    #if !DEBUG
    internal struct SingletonScope<T, H> 
    #else
    internal sealed class SingletonScope<T, H> 
    #endif
        : IDisposable 
        where H : ISingletonScopeHelper<T>, new()
    {
        private T _old;

        public SingletonScope(T temp)
        {
            _old = Helper.Install(temp);
        }

        private static H Helper
        {
            get { return new H(); }
        }

        public void Dispose()
        {
            //
            // First, transfer fields to stack then nuke the fields.
            //

            var old = _old;
            _old = default(T);

            //
            // Shazam! Restore the old value.
            //

            Helper.Restore(old);

            #if DEBUG
            GC.SuppressFinalize(this); // Only when defined as a class!
            #endif
        }

        #if DEBUG

        //
        // This finalizer is used to detect an undisposed scope. This will
        // only indicate that the scope was not disposed but (unfortunately)
        // not which one and where since GC will probably collect much later
        // than it should have been disposed.
        //

        ~SingletonScope()
        {
            Debug.Fail("Scope for " + typeof(T).FullName + " not disposed!");
        }

        #endif
    }
}

Полный рабочий пример доступен по адресу http://gist.github.com/102424 с инструкциями по компиляции, но имейте в виду, что проблему пока невозможно воспроизвести детерминистически.

Ответы [ 5 ]

4 голосов
/ 27 апреля 2009

Одна странность, которую вы можете увидеть, заключается в том, что финализатор все еще может работать, даже когда метод экземпляра все еще работает, при условии, что этот метод экземпляра в дальнейшем не использует никаких переменных. Итак, в вашем примере кода метод Dispose не использует никаких переменных экземпляра после первой строки. Затем экземпляр может быть завершен, даже если Dispose все еще работает.

Если вы вставите вызов в GC.KeepAlive(this) в конце метода Dispose, вы можете обнаружить, что проблема исчезла.

У Криса Брумма есть сообщение в блоге об этом, и я думаю, что где-то еще есть ...

3 голосов
/ 27 апреля 2009

Я всегда использую этот шаблон проектирования для реализации интерфейса IDisposable. (что предлагает Microsoft), и для меня GC.SuppressFinalize всегда имеет гарантию!

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.
    }
}

Источник: MSDN: метод GC.SuppressFinalize

1 голос
/ 16 июля 2009

Я выбрасываю InvalidOperationException в финализаторе, который облегчает поиск типов, которые не были правильно расположены. Когда вызывается Dispose (), когда вызывается GC.SuppressFinalize, я никогда не получаю исключение.

0 голосов
/ 16 июля 2009

Когда создается объект с определенным пользователем финализатором, среда выполнения должна сохранять внутреннюю ссылку на него, поэтому, когда он становится недоступным в пользовательском коде, он может вызывать финализатор в потоке финализации среды выполнения. Учитывая, что время имеет значение при вызове финализаторов, нет смысла хранить объекты в очереди, если пользователь потребовал их подавления. В моей тестовой реализации CLI я держу флаг SuppressFinalizer в заголовке объектов, которые имеют определенные финализаторы. Если флаг имеет значение true, когда поток финализатора достигает этого объекта в очереди, вызов финализатора пропускается. Я не удаляю объект из очереди, поэтому я могу сохранить вызов GC.SuppressFinalize() O (1) вместо O ( N ), где N - количество выделенных финализируемые объекты (я мог бы изменить эту политику на политику отложенного удаления позже).

0 голосов
/ 27 апреля 2009

Я использовал один и тот же шаблон много раз, и GC.SupressFinalize всегда работал.

Имейте в виду, что вызов GC.ReRegisterForFinalize приведет к повторной регистрации объектов для завершения.

Всякий раз, когда я использую описанную выше технику, я всегда гарантирую, что при построении объекта включаю полную трассировку стека, чтобы можно было отследить метод, который выделил неразмещенный объект.

Например. в конструкторе используйте

StackFrame frame = new StackFrame(1);

и сообщите об этом в отладочном сообщении во время финализатора.

Кроме того, я заметил, что ваш GC.SupressFinalize не входит в предложение finally; если во время удаления возникает исключение, ваш финализатор объектов не будет подавлен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...