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

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

这是微软的一些我不知道或我不理解的错误吗?

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

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

只是为了信息…在超级猫的回答之后,我用提出的解决方案修复了我的代码并且它工作得很好:-) !!! 糟糕的是静态字典的使用和它的性能,但它的工作原理。 ……几分钟后,我意识到SuperCat给了我更好的提示,解决静态字典并且我做了。 代码示例如下:

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

样本…

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(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 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.CompilerServices.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(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 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(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 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(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 } } // ********************************************************************** } 

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

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

垃圾收集有两个方面,你没有指望:

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

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

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

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

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

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