3

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?

Community
  • 1
  • 1
avo
  • 10,101
  • 13
  • 53
  • 81

1 Answers1

2

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.

Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78
  • That's it, you nailed it down! `WeakReference` gets GC'ed before `Test` and that's why it returns `false` I recon, despite the `_child` is still alive. Pinning the `WeakReference` makes it return `true`. – avo Aug 25 '14 at 09:55
  • Yes that's almost right but if you see your previous question & answer: the WeakReference itself is not garbage collected(atleast not at that point), its Finalizer is called(which makes it return false). By pinning it, you essentially say that finalizer can't be run for WeakReference. – Erti-Chris Eelmaa Aug 25 '14 at 09:57
  • Right. To stay on the safe side, I can simply do `lock (_gch.Target) {}` inside `DoTest()` as `GCHandle` is a value type. – avo Aug 25 '14 at 09:59
  • What .NET version did you get the `Finalize` code from? For me, it is an internal call. I'm on 4.5.2 or so. – usr Aug 25 '14 at 13:35
  • I took it from here: http://www.dotnetframework.org/default.aspx/4@0/4@0/untmp/DEVDIV_TFS/Dev10/Releases/RTMRel/ndp/clr/src/BCL/System/WeakReference@cs/1305376/WeakReference@cs Seems to be 4.0 – Erti-Chris Eelmaa Aug 25 '14 at 20:38
  • Is there a clean way to ensure that a long weak reference will remain valid as long as its target still exists? IMHO, the finalizer for WeakReference should have tested whether its target was still valid, and reregistered itself for finalization (without releasing the handle) if so. Because objects cannot prevent themselves from being resurrected between the time the system decides their finalizer should run and the time it actually does, it would seem the best way to guard against that would be to have a private object with a finalizer hold a long weak reference to its owner... – supercat Oct 15 '14 at 20:25
  • ...and before doing finalizer cleanup, make sure the owner *no longer exists*. If the owner still exists, figure the `Finalize` call was a false alarm and reregister for cleanup. Unfortunately, I know of no way to easily keep a long weak reference that would be usable in that scenario. – supercat Oct 15 '14 at 20:27