为什么WeakReference.IsAlive变得虚假?
作为这个问题的后续,我有以下代码:
using System; using System.Runtime.InteropServices; namespace ConsoleApplication1 { class Program { class Child { public override string ToString() { return "I am a child!"; } ~Child() { Console.WriteLine("~Child called"); } } class Test { readonly object _child; readonly WeakReference _ref; readonly GCHandle _gch; // GCHandle is a value type, so it's safe public Test() { _child = new Child(); _ref = new WeakReference(_child); _gch = GCHandle.Alloc(_child); } // ... public void DoTest() { lock (_child) { Console.WriteLine("DoTest called, child: " + _child.ToString() + ", is alive: " + _ref.IsAlive); } } ~Test() { Console.WriteLine("~Test starts"); DoTest(); _gch.Free(); Console.WriteLine("~Test ends"); } } static void Main(string[] args) { var test = new Test(); test.DoTest(); test = null; GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); System.Threading.Thread.Sleep(1000); GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); Console.ReadLine(); } } }
输出:
DoTest打电话给,孩子:我还是个孩子!还活着:真的 〜测试开始 DoTest打电话给,孩子:我还是个孩子!,还活着:是的 〜测试结束 〜孩子叫
问题:为什么_child
WeakReference.IsAlive
在~Test()
变为false
,而_child
对象仍然用GCHandle.Alloc
固定下来?
好吧,我记得从终结器访问“类实例变量”并不是一个好主意,因为它们可能处于“随机”状态? 这基本上意味着WeakReference终结器将在您的类终结器之前调用。
有一个特殊的运行时线程专用于调用Finalize方法。 当可释放队列为空(通常是这种情况)时,该线程将hibernate。 但是当条目出现时,该线程会唤醒,从队列中删除每个条目,并调用每个对象的Finalize方法。 因此,您不应该在Finalize方法中执行任何代码,该方法对正在执行代码的线程做出任何假设。 例如,避免在Finalize方法中访问线程本地存储。
http://msdn.microsoft.com/en-us/magazine/bb985010.aspx
如果您确定了WeakReference,您可以获得更有意义的结果:
public Test() { _child = new Child(); _ref = new WeakReference(_child); _gch = GCHandle.Alloc(_child); _test = GCHandle.Alloc(_ref); }
如果让GC知道现在无法收集WeakReference类ITSELF ,您可以获得相同的结果,如下所示:
static void Main(string[] args) { var test = new Test(); var win = new WeakReference(test._child); test._ref = win;//new WeakReference(test._child); test.DoTest(); test = null; }
来自WeakReference的实际代码:
~WeakReference() { IntPtr old_handle = m_handle; if (old_handle != IntPtr.Zero) { if (old_handle == Interlocked.CompareExchange(ref m_handle, IntPtr.Zero, old_handle)) GCHandle.InternalFree(old_handle); } }
你可以看到它在终结器运行后释放手柄,将其设置为零并且IsAlive现在将报告为假。 引用本身实际上是活着的。