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

问题描述 投票:3回答:3

您好,我在这里有不了解为何达到断点的代码(请参见注释)。

这是我不知道还是不正确理解的Microsoft bug?

该代码已在Debug中测试,但我认为它不应更改任何内容。

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

仅用于信息...在超级猫的回答之后,我用建议的解决方案修复了我的代码,它很好用:-) !!!不好的事情是静态字典的使用以及它所具有的性能,但是它可以工作。...几分钟后,我意识到SuperCat会给我所有更好的提示,以解决静态字典,我做到了。代码示例是:

  1. 带有错误的代码
  2. 已更正代码,但具有静态的ConditionalWeakTable
  3. 带有ConditioalWeakTable的代码,其中包含SuperCat技巧(非常感谢他!)

样品...

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

    // **********************************************************************
}

带有ConditioalWeakTable的代码,其中包含SuperCat技巧(非常感谢他!)

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

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

}
c# garbage-collection weak-references finalizer
3个回答
5
投票

WeakReference有时令人讨厌的局限性是,如果不存在对WeakReference itself]的强根引用,WeakReference可能会失效,即使trackResurrection构造函数参数也可能发生这种情况是true,并且即使WeakReference的目标已扎根]。此行为源于以下事实:WeakReference具有不受管的资源(GC句柄),并且如果WeakReference的终结器没有清理GC句柄,则它将永远不会被清理,并且会构成内存泄漏。

如果对象的终结器有必要使用WeakReference对象,则该对象必须做出某些规定以确保强烈引用那些对象。我不确定实现此目标的最佳模式是什么,但是.net 4.0中添加的ConditionalWeakTable<TKey,TValue>可能有用。它有点像Dictionary<TKey,TValue>,除了只要表本身被强力引用并且给定键被强力引用,其对应的值也将被视为强引用。请注意,如果ConditionalWeakTable保留将X链接到Y,并将Y链接到表的条目,则只要保留X或Y,表也将保留。

[您没有指望的垃圾收集中有[[two

个方面:
  • WeakReference.IsAlive变为false的确切时间。您的代码隐式地假定终结器运行时会发生这种情况。事实并非如此,它发生在对象被垃圾回收时。之后,由于该对象具有终结器且未调用GC.SuppressFinalize(),因此将其放置在终结器队列中,等待终结器线程完成其工作。因此,有一段时间IsAlive为false,但〜B()尚未运行。

  • 对象完成的顺序是不可预测的。您隐式地假设B在A之前完成。您无法进行此假设。
  • B.Dispose()方法中也存在一个错误,当客户端代码显式放置该对象时,它将无法正确计算B实例。您尚未遇到该错误。

    没有合理的方法来修复此代码。此外,它还测试了已经由CLR提供的严格保证支持的东西。只需将其删除。

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

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


    4
    投票

    [您没有指望的垃圾收集中有[[two


    2
    投票
    WeakReference _weakB与对象a同时可用于垃圾回收。您这里没有顺序保证,因此很有可能_weakB在对象a之前完成。

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

    © www.soinside.com 2019 - 2024. All rights reserved.