问题
As a follow-up to this question, I have the following code:
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();
}
}
}
The output:
DoTest called, child: I am a child!, is alive: True ~Test starts DoTest called, child: I am a child!, is alive: False ~Test ends ~Child called
The question: why does WeakReference.IsAlive
for _child
become false
inside ~Test()
, while the _child
object is still pinned down with GCHandle.Alloc
?
回答1:
Well, I remember that accessing "class instance variables" from finalizer is not a good idea, as they could be in "random" state? This basically means that WeakReference finalizer will be called before your class finalizer.
There is a special runtime thread dedicated to calling Finalize methods. When the freachable queue is empty (which is usually the case), this thread sleeps. But when entries appear, this thread wakes, removes each entry from the queue, and calls each object's Finalize method. Because of this, you should not execute any code in a Finalize method that makes any assumption about the thread that's executing the code. For example, avoid accessing thread local storage in the Finalize method.
http://msdn.microsoft.com/en-us/magazine/bb985010.aspx
If you pin down your WeakReference, you can get more meaningful results:
public Test()
{
_child = new Child();
_ref = new WeakReference(_child);
_gch = GCHandle.Alloc(_child);
_test = GCHandle.Alloc(_ref);
}
You can get the same results if you let the GC know that WeakReference class ITSELF can't be collected for now, as such:
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;
}
The actual code from 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);
}
}
YOu can see that it releases the handle once it's finalizer has been run, sets it zero & IsAlive will report false now. The reference itself is actually alive though.
来源:https://stackoverflow.com/questions/25482453/why-weakreference-isalive-becomes-false