Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
364 views
in Technique[技术] by (71.8m points)

garbage collection - C# WeakReference object is NULL in finalizer although still strongly referenced

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

Is this a Microsoft bug of something I don't know or I don't understand properly ?

The code was tested in Debug but I think it should not changes anything.

Note: You can test the code directly in a console app.

JUST FOR INFORMATION... following supercat answer, I fixed my code with proposed solution and it works nicely :-) !!! The bad thing is the usage of a static dict and the performance the goes with it but it works. ... After few minutes, I realized that SuperCat give me all hints to do it better, to workaround the static dictionary and I did it. Code samples are:

  1. Code with the bug
  2. Code corrected but with a static ConditionalWeakTable
  3. Code with ConditioalWeakTable that include the SuperCat tricks (thanks so much 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 corrected:

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 that include the SuperCat tricks (thanks so much 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);
        }
    }

    // **********************************************************************

}

Following question of CitizenInsane... I don't remember exactly why I did what I did... I found my sample but wasn't sure about my intention at that time. I tried to figure it out and came with the following code which I thing is more clear but still don't remember my original 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 B B
        {
            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(a.B); // 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(a.B);

            B.AllBs.Clear();
            a.B = null;

            GC.Collect(GC.MaxGeneration);
            GC.WaitForPendingFinalizers();

            Debug.Assert(!weakB.IsAlive); // Need  second pass of Collection to be collected
        }
    }

    // **********************************************************************

}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

A sometimes-irksome limitation of WeakReference is that a WeakReference may be invalidated if no strongly-rooted reference exists to the WeakReference itself, and this may occur even if the trackResurrection constructor parameter was true, and even if the target of the WeakReference is strongly rooted. This behavior stems from the fact that a WeakReference has an unmanaged resource (a GC handle) and if the finalizer for the WeakReference didn't clean up the GC handle, it would never get cleaned up and would constitute a memory leak.

If it will be necessary for an object's finalizers to make use of WeakReference objects, the object must make some provision to ensure that those objects remain strongly referenced. I'm not sure what the best pattern is to accomplish this, but the ConditionalWeakTable<TKey,TValue> that was added in .net 4.0 may be useful. It's a little bit like Dictionary<TKey,TValue> except that as long as a table itself is strongly referenced and a given key is strongly referenced, its corresponding value will be regarded as strongly referenced. Note that if a ConditionalWeakTable holds an entry linking X to Y, and Y to the table, then as long as X or Y remains, the table will remain as well.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...