GC.SuppressFinalize est-il garanti?

Mon observation pratique a été que GC.SuppressFinalize ne supprime pas toujours l’appel du finaliseur. Il se peut que le finaliseur se nomme non-infidèle. Je me demande donc si GC.SuppressFinalize a la nature d’une demande plutôt qu’une garantie par le système?


Plus d’information

Les informations suivantes peuvent aider à fournir plus de contexte à la question si nécessaire.

Le GC.SuppressFinalize document GC.SuppressFinalize indique qu’il s’agit d’une demande:

Demande que le système n’appelle pas le finaliseur pour l’object spécifié.

Je me demande s’il s’agissait d’une utilisation occasionnelle du mot ou vraiment destinée à décrire le comportement au moment de l’exécution.

J’ai observé cela avec la classe SingletonScope suivante tirée du projet Schnell , qui était basée sur une idée originale de Ian Griffiths, sauf qu’elle est plus généralisée. L’idée est de détecter, dans les versions de débogage, si la méthode Dispose a été appelée ou non. Si ce n’est pas le cas, le finalisateur finira par entrer en jeu et l’on peut afficher un avertissement. Si Dispose est appelé, alors GC.SuppressFinalize doit empêcher le finaliseur de se déclencher. Malheureusement, les avertissements semblent se déclencher de toute façon, mais pas de manière déterministe. C’est-à-dire qu’ils ne tirent pas à chaque course.

 #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 redissortingbute 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 dissortingbuted 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. For example, if the type of value to scope // is ThreadPriority then use ISingletonScopeHelper // and SingletonScope. // // // In debug builds, this type is defined as a class so a finalizer // can be used to detect an undisposed scope. // ///  /// 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. ///  #if !DEBUG internal struct SingletonScope #else internal sealed class SingletonScope #endif : IDisposable where H : ISingletonScopeHelper, 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 } } 

Un exemple de travail complet est disponible à l’ adresse http://gist.github.com/102424 avec les instructions de compilation, mais notez que le problème ne peut pas être reproduit jusqu’à présent de manière déterministe.

Vous remarquerez peut- être une chose étrange: le finaliseur peut toujours être exécuté même si une méthode d’instance est toujours en cours d’exécution, tant que cette méthode d’instance n’utilise aucune variable par la suite. Ainsi, dans votre exemple de code, la méthode Dispose n’utilise aucune variable d’instance après la première ligne. L’instance peut ensuite être finalisée, même si Dispose est toujours en cours d’exécution.

Si vous insérez un appel à GC.KeepAlive(this) à la fin de la méthode Dispose , vous constaterez peut- être que le problème disparaît.

Chris Brumme a publié un article sur ce blog , et je pense qu’il y en a un autre quelque part …

J’utilise toujours ce modèle pour implémenter l’interface IDisposable. (ce qui est suggéré par Microsoft) et pour moi GC.SuppressFinalize a toujours la nature d’une garantie!

 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: méthode GC.SuppressFinalize

Je jette une exception InvalidOperationException dans le finaliseur, ce qui facilite la recherche de types qui n’ont pas été éliminés correctement. Lorsque Dispose () est appelé lorsque GC.SuppressFinalize est appelé, je n’obtiens jamais d’exception.

J’ai utilisé le même motif plusieurs fois et GC.SupressFinalize a toujours semblé fonctionner.

Gardez à l’esprit qu’un appel à GC.ReRegisterForFinalize entraînera une réinscription des objects pour la finalisation.

Chaque fois que j’utilise la technique ci-dessus, je m’assure toujours d’inclure une trace de stack complète lors de la construction de l’object afin de pouvoir retrouver la méthode qui a affecté l’object non éliminé.

Par exemple. dans l’utilisation du constructeur

 StackFrame frame = new StackFrame(1); 

et le signaler dans votre message de débogage lors du finaliseur.

De plus, je remarque que votre GC.SupressFinalize n’est pas dans une clause finally, si une exception est levée lors de la suppression, le finaliseur de vos objects ne sera pas supprimé.

Lorsqu’un object avec un finaliseur défini par l’utilisateur est construit, le moteur d’exécution doit conserver une référence interne à celui-ci. Ainsi, lorsqu’il devient inaccessible dans le code utilisateur, le finaliseur peut toujours être appelé dans le processus de finalisation du moteur d’exécution. Considérant que le temps presse lors de l’appel des finaliseurs, il n’a aucun sens de conserver les objects dans la file d’attente si l’utilisateur a demandé à ce qu’ils soient supprimés. Dans mon implémentation de test CLI, je conserve un indicateur SuppressFinalizer dans l’en-tête des objects dotés de finaliseurs définis par l’utilisateur. Si l’indicateur est vrai lorsque le thread de finalisation atteint cet object dans la queue, l’appel du finaliseur est ignoré. Je ne supprime pas l’object de la queue afin de pouvoir conserver l’appel à GC.SuppressFinalize() O (1) au lieu de O ( N ), où N est le nombre d’objects finalisables alloués (je pourrais modifier cette règle en politique d’éloignement différé ultérieurement).