Wrong answer #1: Your own ThreadLocal

time to read 3 min | 597 words

Originally posted at 12/15/2010

Well, one way we can use to solve the problem is by introducing a finalizer, like so:

public class CloseableThreadLocal
{
    [ThreadStatic] private static Dictionary<object, object> slots;

    public static Dictionary<object, object> Slots
    {
        get { return slots ?? (slots = new Dictionary<object, object>()); }
    }

    public /*protected internal*/ virtual Object InitialValue()
    {
        return null;
    }

    public virtual Object Get()
    {
        object val;

        if (Slots.TryGetValue(this, out val))
        {
            return val;
        }
        val = InitialValue();
        Set(val);
        return val;
    }

    public virtual void Set(object val)
    {
        Slots[this] = val;
    }

    public virtual void Close()
    {
        GC.SuppressFinalize(this);
        if (slots != null)// intentionally using the field here, to avoid creating the instance
            slots.Remove(this);
    }

   ~CloseableThreadLocal()
   {
         Close();
   }
}

But this will not actually work, executing this code shows that we still have a memory leak:

internal class Program
{
    private static void Main(string[] args)
    {
        UseThreadLocal();
        GC.Collect(int.MaxValue);
        GC.WaitForPendingFinalizers();
        Console.WriteLine(CloseableThreadLocal.slots.Count);
    }

    private static void UseThreadLocal()
    {
        var tl = new CloseableThreadLocal();
        tl.Set("hello there");
        Console.WriteLine(tl.Get());
    }
}

Why? And how can we fix this?