L’object C # WeakReference a la valeur NULL dans le finaliseur bien qu’il soit toujours fortement référencé

Salut, j’ai le code ici où je ne comprends pas pourquoi je frappe le point d’arrêt (voir commentaire).

Est-ce un bug de Microsoft lié à quelque chose que je ne connais pas ou que je ne comprends pas bien?

Le code a été testé dans Debug mais je pense qu’il ne devrait rien changer.

Remarque: vous pouvez tester le code directement dans une application de console.

JUSTE POUR INFORMATION … suite à la réponse de supercat, j’ai corrigé mon code avec la solution proposée et cela fonctionne bien 🙂 !!! La mauvaise chose est l’utilisation d’un dict statique et les performances qui vont avec, mais ça marche. … Après quelques minutes, je me suis rendu compte que SuperCat me donnait tous les conseils pour le faire mieux, pour contourner le dictionnaire statique et je l’ai fait. Les exemples de code sont:

  1. Code avec le bug
  2. Code corrigé mais avec un statique ConditionalWeakTable
  3. Code avec ConditioalWeakTable qui inclut les astuces SuperCat (merci beaucoup à lui!)

Des échantillons …

using System; using System.Collections.Generic; using System.Diagnostics; namespace WeakrefBug { // ********************************************************************** class B : IDisposable { public static List AllBs = new List(); public B() { AllBs.Add(this); } private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposed) { AllBs.Remove(this); disposed = true; } } ~B() { Dispose(false); } } // ********************************************************************** class A { WeakReference _weakB = new WeakReference(new B()); ~A() { B b = _weakB.Target as B; if (b == null) { if (B.AllBs.Count == 1) { Debugger.Break(); // b Is still referenced but my weak reference can't find it, why ? } } else { b.Dispose(); } } } // ********************************************************************** class Program { static void Main(ssortingng[] args) { A a = new A(); a = null; GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); } } // ********************************************************************** } 

Version corrigée:

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.ComstackrServices; namespace WeakrefBug // Working fine with ConditionalWeakTable { // ********************************************************************** class B : IDisposable { public static List AllBs = new List(); public B() { AllBs.Add(this); } private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposed) { AllBs.Remove(this); disposed = true; } } ~B() { Dispose(false); } } // ********************************************************************** class A { private static readonly System.Runtime.ComstackrServices.ConditionalWeakTable WeakBs = new ConditionalWeakTable(); public A() { WeakBs.Add(this, new B()); } public B CreateNewB() { B b = new B(); WeakBs.Remove(this); WeakBs.Add(this, b); return b; } ~A() { B b; WeakBs.TryGetValue(this, out b); if (b == null) { if (B.AllBs.Count == 1) { Debugger.Break(); // B Is still referenced but my weak reference can't find it, why ? } } else { b.Dispose(); } } } // ********************************************************************** class Program { static void Main(ssortingng[] args) { A a = new A(); WeakReference weakB = new WeakReference(a.CreateNewB()); // Usually don't need the internal value, but only to ensure proper functionnality a = null; GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); Debug.Assert(!weakB.IsAlive); } } // ********************************************************************** } 

Code avec ConditioalWeakTable qui inclut les astuces SuperCat (merci beaucoup à lui!)

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.ComstackrServices; namespace WeakrefBug // Working fine with non static ConditionalWeakTable - auto cleanup { // ********************************************************************** class B : IDisposable { public static List AllBs = new List(); public B() { AllBs.Add(this); } private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposed) { AllBs.Remove(this); disposed = true; } } ~B() { Dispose(false); } } // ********************************************************************** class A { private ConditionalWeakTable _weakBs = null; public A() { } public B CreateNewB() { B b = new B(); if (_weakBs == null) { _weakBs = new ConditionalWeakTable(); _weakBs.Add(b, _weakBs); } _weakBs.Remove(this); _weakBs.Add(this, b); return b; } internal ConditionalWeakTable ConditionalWeakTable // TestOnly { get { return _weakBs; } } ~A() { object objB; _weakBs.TryGetValue(this, out objB); if (objB == null) { if (B.AllBs.Count == 1) { Debugger.Break(); // B Is still referenced but my weak reference can't find it, why ? } } else { ((B)objB).Dispose(); } } } // ********************************************************************** class Program { static void Main(ssortingng[] args) { A a = new A(); WeakReference weakB = new WeakReference(a.CreateNewB()); // Usually don't need the internal value, but only to ensure proper functionnality WeakReference weakConditionalWeakTable = new WeakReference(a.ConditionalWeakTable); a = null; GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); Debug.Assert(!weakB.IsAlive); Debug.Assert(!weakConditionalWeakTable.IsAlive); } } // ********************************************************************** } 

Après la question de CitizenInsane … Je ne me souviens pas exactement pourquoi j’ai fait ce que j’ai fait … J’ai trouvé mon échantillon mais je n’étais pas sûr de mon intention à ce moment-là. J’ai essayé de le comprendre et je suis arrivé avec le code suivant qui est plus clair mais ne me souviens toujours pas de mon besoin initial. Pardon ???

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.ComstackrServices; namespace WeakrefBug // Working fine with ConditionalWeakTable { // ********************************************************************** class B : IDisposable { public static List AllBs = new List(); public B() { AllBs.Add(this); } private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposed) { AllBs.Remove(this); disposed = true; } } ~B() { Dispose(false); } } // ********************************************************************** class A { private ConditionalWeakTable _weakBs = null; private WeakReference _weakB = null; public A() { _weakBs = new ConditionalWeakTable(); B b = new B(); _weakB = new WeakReference(b); _weakBs.Add(b, _weakB); } public BB { get { return _weakB.Target as B; } set { _weakB.Target = value; } } internal ConditionalWeakTable ConditionalWeakTable // TestOnly { get { return _weakBs; } } ~A() { B objB = B; if (objB == null) { if (B.AllBs.Count == 1) { Debugger.Break(); // B Is still referenced but my weak reference can't find it, why ? } } else { ((B)objB).Dispose(); } } } // ********************************************************************** class Program { static void Main(ssortingng[] args) { Test1(); Test2(); } private static void Test1() { A a = new A(); WeakReference weakB = new WeakReference(aB); // Usually don't need the internal value, but only to ensure proper functionnality WeakReference weakConditionalWeakTable = new WeakReference(a.ConditionalWeakTable); a = null; GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); Debug.Assert(B.AllBs.Count == 0); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); Debug.Assert(!weakB.IsAlive); // Need second pass of Collection to be collected Debug.Assert(!weakConditionalWeakTable.IsAlive); } private static void Test2() { A a = new A(); WeakReference weakB = new WeakReference(aB); B.AllBs.Clear(); aB = null; GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); Debug.Assert(!weakB.IsAlive); // Need second pass of Collection to be collected } } // ********************************************************************** } 

Une limitation parfois WeakReference de WeakReference est qu’un WeakReference peut être invalidé si aucune référence fortement enracinée n’existe pour le WeakReference luimême , ce qui peut se produire même si le paramètre du constructeur trackResurrection était true et même si la cible du constructeur WeakReference était forte. enracinée . Ce comportement provient du fait que WeakReference a une ressource non gérée (un handle de GC) et si le finaliseur de WeakReference ne nettoie pas le handle de GC, il ne sera jamais nettoyé et constituerait une fuite de mémoire.

S’il est nécessaire que les finaliseurs d’un object utilisent des objects WeakReference , l’object doit prendre des dispositions pour s’assurer que ces objects restnt fortement référencés. Je ne sais pas quel est le meilleur modèle pour accomplir cela, mais le ConditionalWeakTable ajouté dans .net 4.0 peut être utile. C’est un peu comme Dictionary sauf que tant que la table elle-même est fortement référencée et qu’une clé donnée est fortement référencée, sa valeur correspondante sera considérée comme fortement référencée. Notez que si un ConditionalWeakTable contient une entrée reliant X à Y et Y à la table, tant que X ou Y restnt, la table restra également.

Il y a deux aspects de la récupération de place sur lesquels vous n’avez pas compté:

  • L’heure exacte à laquelle le WeakReference.IsAlive devient faux. Votre code suppose implicitement que cela se produira lors de l’exécution du finaliseur. Ce n’est pas le cas, cela se produit lorsque l’object est récupéré. Après quoi, l’object est placé dans la queue du finaliseur, car il possède un finaliseur et que GC.SuppressFinalize () n’a pas été appelé, attendant que le thread du finaliseur fasse son travail. Il y a donc une période de temps où IsAlive est faux mais ~ B () n’a pas encore été exécuté.

  • L’ordre dans lequel les objects sont finalisés n’est pas prévisible. Vous supposez implicitement que B est finalisé avant A. Vous ne pouvez pas faire cette hypothèse.

Il y a aussi un bogue dans la méthode B.Dispose (), elle ne comptera pas correctement les instances B lorsque le code client a explicitement supprimé l’object. Vous n’avez pas encore touché ce bogue.

Il n’y a pas de moyen raisonnable de corriger ce code. De plus, il teste quelque chose qui est déjà soutenu par des garanties fermes fournies par le CLR. Il suffit de l’enlever.

WeakReference _weakB est disponible pour la récupération de place en même temps que l’object a . Vous n’avez pas de garantie d’ordre ici, donc il se pourrait très bien que _weakB soit finalisé avant l’object a .

Accéder à _weakB dans le finaliseur de A est dangereux car vous ne connaissez pas l’état de _weakB . J’imagine que dans votre cas, il a été finalisé et que cela le renvoie à null pour .Target .