Гарантируется ли GC.SuppressFinalize?
Мое наблюдение на практике заключалось в том, что GC.SuppressFinalize
не всегда подавляет вызов финализатора. Может быть, финализатор называется беспредметным. Поэтому я задаюсь вопросом, имеет ли GC.SuppressFinalize
характер запроса, а не гарантию системы?
Дополнительная информация
Следующая информация может помочь предоставить больше контекста для quesiton, если это необходимо.
В документе 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 с инструкциями компиляции, но обратите внимание, что проблема пока не может быть детерминированна.
Ответы
Ответ 1
Одна странность, которую вы можете видеть, заключается в том, что финализатор может работать даже в том случае, если метод экземпляра все еще запущен, если этот метод экземпляра не использует какие-либо переменные позже. Таким образом, в вашем примере кода метод Dispose
не использует переменные экземпляра после первой строки. Экземпляр может быть завершен, хотя Dispose
все еще работает.
Если вы вставляете вызов в GC.KeepAlive(this)
в конце метода Dispose
, проблема может исчезнуть.
У Chris Brumme есть сообщение в блоге об этом, и я думаю, что там где-то где-то...
Ответ 2
Я всегда использую этот шаблон проектирования для реализации интерфейса 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 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 Method
Ответ 3
Я делаю исключение InvalidOperationException в финализаторе, что позволяет легко находить типы, которые не были правильно расположены. Когда Dispose() вызывается там, где вызывается GC.SuppressFinalize, я никогда не получаю исключение.
Ответ 4
Я использовал ту же схему много раз, и GC.SupressFinalize всегда работал.
Имейте в виду, что вызов GC.ReRegisterForFinalize заставит объекты перерегистрироваться для завершения.
Всякий раз, когда я использую описанную выше технику, я всегда гарантирую, что во время построения объекта включаю полную трассировку стека, чтобы я мог отслеживать метод, который выделял объект, который не был удален.
Eg. в конструкторе используйте
StackFrame frame = new StackFrame(1);
и сообщите об этом в своем отладочном сообщении во время финализатора.
Кроме того, я замечаю, что ваш GC.SupressFinalize не находится в предложении finally, если во время удаления выбрано исключение, финализатор объектов не будет подавлен.
Ответ 5
Когда объект с определяемым пользователем финализатором сконструирован, среда выполнения должна содержать внутреннюю ссылку на нее, поэтому, когда она становится недоступной в пользовательском коде, она может по-прежнему иметь финализатор, вызываемый в потоке завершения выполнения. Учитывая, что время имеет решающее значение при вызове финализаторов, нет смысла хранить объекты в очереди, если пользователь запросил их подавить. В моей тестовой реализации CLI я сохраняю флаг SuppressFinalizer в заголовке объектов с определенными пользователем финализаторами. Если флаг верен, когда поток финализатора достигает этого объекта в очереди, вызов финализатора пропускается. Я не удаляю объект из очереди, поэтому я могу сохранить вызов GC.SuppressFinalize()
O (1) вместо O (N), где N - количество выделенных финализируемых объектов (я мог бы изменить эту политику на отложенное удаление политика позже).