Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why WeakReference.IsAlive becomes false?

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?

like image 221
avo Avatar asked Sep 20 '26 18:09

avo


1 Answers

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.

like image 82
Erti-Chris Eelmaa Avatar answered Sep 23 '26 12:09

Erti-Chris Eelmaa



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!