尽管仍然强烈引用,C#WeakReference对象在终结器中为NULL

时间:2013-02-26 16:05:25

标签: c# garbage-collection weak-references finalizer

嗨,我在这里有代码,我不明白为什么我会遇到断点(见评论)。

这是微软我不了解或不理解的错误吗?

代码在Debug中测试过,但我认为它不会改变任何东西。

注意:您可以直接在控制台应用中测试代码。

JUST FOR INFORMATION ...在超级猫的回答之后,我用提出的解决方案修复了我的代码并且它运行良好:-) !!!坏的是静态字典的使用和它的性能,但它的工作原理。 ......几分钟后,我意识到SuperCat给了我更好的提示,解决静态字典并且我做了。代码示例如下:

  1. 带有错误的代码
  2. 已更正代码但使用静态ConditionalWeakTable
  3. 包含SuperCat技巧的ConditioalWeakTable代码(非常感谢他!)
  4. 样品...

    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();
        }
        }
    
        // **********************************************************************
    }
    

    修正版本:

    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);
            }
        }
    
        // **********************************************************************
    }
    

    包含SuperCat技巧的ConditioalWeakTable代码(非常感谢他!)

    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);
            }
        }
    
        // **********************************************************************
    
    }
    

    关于CitizenInsane的问题...... 我不记得为什么我做了我做的事情......我找到了我的样本,但当时并不确定我的用意。我试图搞清楚,并附带以下代码,我更清楚,但仍然不记得我原来的需要。对不起???

    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
            }
        }
    
        // **********************************************************************
    
    }
    

3 个答案:

答案 0 :(得分:4)

您没有依赖垃圾收集的两个方面:

  • WeakReference.IsAlive变为false的确切时间。您的代码隐含地假设在终结器运行时会发生。事实并非如此,它发生在对象被垃圾收集时。之后将对象放在终结器队列上,因为它有一个终结器并且没有调用GC.SuppressFinalize(),等待终结器线程完成它的工作。所以有一段时间IsAlive是假的但是〜B()还没有运行。

  • 对象最终确定的顺序是不可预测的。你隐含地假设B在A之前完成。你不能做出这个假设。

B.Dispose()方法中还存在一个错误,当客户端代码显式处理对象时,它不会正确计算B实例。你还没有碰到那个bug。

没有合理的方法来修复此代码。此外,它还测试了CLR提供的硬保证所支持的内容。只需删除它。

答案 1 :(得分:4)

WeakReference的有时令人厌烦的限制是WeakReference如果WeakReference 本身不存在强根引用,则trackResurrection可能无效,并且即使true构造函数参数为WeakReference,也可能出现这种情况,即使WeakReference的目标为,也可能出现。这种行为源于WeakReference具有非托管资源(GC句柄)的事实,如果WeakReference的终结器没有清理GC句柄,它将永远不会被清理干净构成内存泄漏。

如果对象的终结器需要使用ConditionalWeakTable<TKey,TValue>个对象,则该对象必须进行一些设置以确保这些对象保持强引用。我不确定实现此目的的最佳模式是什么,但在.net 4.0中添加的Dictionary<TKey,TValue>可能很有用。它有点像ConditionalWeakTable,除了强烈引用表本身并强烈引用给定键之外,它的相应值将被视为强引用。请注意,如果{{1}}包含一个将X连接到Y,Y连接到表格的条目,那么只要X或Y保留,表格就会保留。

答案 2 :(得分:2)

WeakReference _weakB可用于与对象a同时进行垃圾回收。您在这里没有订单保证,因此很可能_weakB在对象a之前完成。

在A的终结器中访问_weakB是危险的,因为您不知道_weakB的状态。我猜你的情况已经完成了,那导致它为.Target返回null。