C # WeakReference object is NULL in the finalizer, although it is still strongly referenced

Hi, I have a code here where I don’t understand why I hit the breakpoint (see comment).

Is this a Microsoft bug of something that I don’t know, or I don’t understand correctly?

The code has been tested in Debug, but I think that it should not change anything.

Note. You can check the code directly in the console application.

FOR INFORMATION ONLY ... after the supercat answer, I fixed my code with the proposed solution, and it works fine :-) !!! The bad thing is using a static dictate, and performance is with it, but it works. ... After a few minutes I realized that SuperCat gives me all the hints to make it better, to bypass the static dictionary, and I did it. Code Samples:

  • Error code
  • Code corrected, but with static ConditionalWeakTable
  • Code with ConditioalWeakTable, which includes SuperCat tricks (thanks a lot to him!)

Samples ...

using System; using System.Collections.Generic; using System.Diagnostics; namespace WeakrefBug { // ********************************************************************** class B : IDisposable { public static List<B> AllBs = new List<B>(); 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(string[] args) { A a = new A(); a = null; GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); } } // ********************************************************************** } 

Version fixed:

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; namespace WeakrefBug // Working fine with ConditionalWeakTable { // ********************************************************************** class B : IDisposable { public static List<B> AllBs = new List<B>(); 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.CompilerServices.ConditionalWeakTable<A, B> WeakBs = new ConditionalWeakTable<A, B>(); 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(string[] 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 with ConditioalWeakTable, which includes SuperCat tricks (thanks a lot to him!)

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; namespace WeakrefBug // Working fine with non static ConditionalWeakTable - auto cleanup { // ********************************************************************** class B : IDisposable { public static List<B> AllBs = new List<B>(); 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<object, object> _weakBs = null; public A() { } public B CreateNewB() { B b = new B(); if (_weakBs == null) { _weakBs = new ConditionalWeakTable<object, object>(); _weakBs.Add(b, _weakBs); } _weakBs.Remove(this); _weakBs.Add(this, b); return b; } internal ConditionalWeakTable<object, object> 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(string[] 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); } } // ********************************************************************** } 

The next question is CitizenInsane ... I do not remember exactly why I did what I did ... I found my sample, but at that time I was not sure of my intentions. I tried to understand this and came up with the following code, which I understand more, but still do not remember my initial need. Sorry???

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; namespace WeakrefBug // Working fine with ConditionalWeakTable { // ********************************************************************** class B : IDisposable { public static List<B> AllBs = new List<B>(); 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<object, object> _weakBs = null; private WeakReference _weakB = null; public A() { _weakBs = new ConditionalWeakTable<object, object>(); 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<object, object> 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(string[] 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 } } // ********************************************************************** } 
+3
garbage-collection c # finalizer weak-references
source share
3 answers

The sometimes-unambiguous limitation of WeakReference is that WeakReference can be invalidated if there is no strictly linked reference to WeakReference itself, and this can happen even if the trackResurrection constructor trackResurrection was true , and even if the WeakReference target WeakReference strongly rooted. This behavior stems from the fact that WeakReference has an unmanaged resource (GC descriptor), and if the finalizer for WeakReference not cleared the GC descriptor, it will never be cleared and will be a memory leak.

If WeakReference objects must be used for object WeakReference , the object must make some condition to ensure that these objects are strongly bound. I'm not sure what the best template is for this, but the ConditionalWeakTable<TKey,TValue> , which was added in .net 4.0, may be useful. This is a bit like Dictionary<TKey,TValue> , except that as long as the table itself is strongly bound and the key is strictly bound, its corresponding value will be considered strongly referenced. Note that if a ConditionalWeakTable contains an entry associating X with Y and Y with the table, then as long as X or Y remains, the table also remains.

+3
source share

There are two aspects of garbage collection that you did not expect:

  • The exact time when the value of WeakReference.IsAlive becomes false. Your code implicitly assumes this will happen when the finalizer starts. This is not the case; this happens when an object receives garbage collection. After that, the object is placed in the finalizer queue, because it has a finalizer and GC.SuppressFinalize () was not called, waiting for the finalizer thread to complete its work. So, there is a period of time when IsAlive is false, but ~ B () is not running yet.

  • The order in which objects complete is not predictable. You implicitly assume that B is completed before A. You cannot make this assumption.

There is also an error in the B.Dispose () method, it will not correctly count instances of B when the client code explicitly deletes the object. You have not hit this error yet.

There is no reasonable way to fix this code. In addition, it verifies what is already supported by the stringent guarantees provided by the CLR. Just delete it.

+4
source share

The WeakReference _weakB is available for garbage collection at the same time as object a . Here you do not have an order guarantee, so it is possible that _weakB completed before object a .

Accessing _weakB in finalizer A is dangerous because you do not know the state of _weakB . I assume that in your case this was completed and this forces it to return null for .Target .

+2
source share

All Articles