Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WeakReferences as a Good Caching Mechanism

0.00/5 (No votes)
10 Apr 2009 1  
This article shows that weak references are a good caching mechanism if used with a KeepAlive structure in mind for recently used objects.

Introduction

Many say that weak references are not a good choice for a caching mechanism, but I disagree. In my opinion, weak references are incomplete for a caching mechanism as, if you only have weak references to your cached objects, they will die at every collection. So, to solve this problem, the simplest option is to create a KeepAlive feature that guarantees that recently used objects will not be collected, and then use the weak references, as they will allow you to use your object better, as a simple "timed cache" may reload data that is still in memory.

Background

I created my first caching solution to solve the problem of sending large viewstates to clients and to avoid storing large information in sessions. The idea was simple. When a Cache<T> object was created, it serialized its target object in a temporary directory. The cleanup of such a directory or how I did it to be fast is not the case. When I didn't have the information in memory, I loaded it from the serialized data. When I had it in memory, I simply reused it. But, after a collection, I needed to reload all the cached objects that were actively used. So, I implemented the first version of the GCUtils class, with a method named KeepAlive that allowed an object to live for 30 seconds after its last use, thus surviving collections during this time. Now, I use a much better version that allows the object to survive only to the next collection. After that, if a new KeepAlive is not done, the second collection will collect it.

Using the Code

I have uploaded a zip file with this article with the source code of a DLL of my own, and a sample that shows how to use KeepAliveWeakReference, WeakDictionary, and WeakKeyDictionary. The DLL also has some other useful classes, but for this article, these three classes are the most important ones.

The GCUtils has two important items. The first is the Collected event. I use this method in my WeakDictionary and WeakKeyDictionary to remove collected references. Also, if you see my implementation, I have used the GCHandle for WeakDictionary and WeakKeyDictionary, but that's because the GCHandle is the class that really does the weak reference work, but it is "unsafe" to use as it can leak memory if used improperly.

The Collected event is very useful. For example, you can add a handler to the Collected event to do a TrimExcess in your lists or hashsets. After all, a weak reference will not allow you to free memory by doing a TrimExcess, right?

But, Collected works in another thread, so the collected handler must be thread safe. For example, in the class constructor, you call:

GCUtils.Collected += p_Collected;

And then, you implement p_Collected like:

private void p_Collected()
{
    lock(DisposeLock)
    {
        if (WasDisposed)
            return;

        GCUtils.Collected += p_Collected;

        var oldDictionary = fDictionary;
        var newDictionary = new Dictionary<TKey, ExtendedGCHandle>();
        foreach(var pair in oldDictionary)
        {
            var wr = pair.Value;

            if (wr.IsAlive)
                newDictionary.Add(pair.Key, pair.Value);
            else
                wr.Free();
        }

        fDictionary = newDictionary;
    }
}

This is the code used by WeakDictionary to remove unused items. I must re-register for the Collected event, as the collected list is cleared when it runs. The really important part here is to see the use of the lock keyword, as the entire class must access its dictionary using a lock.

And, the other important item of GCUtils is the KeepAlive method. The GC.KeepAlive is not the same. GC.KeepAlive keeps an object alive until that specific line of code. GCUtils.KeepAlive keeps an object alive for the next collection.

The other interesting classes are KeepAliveWeakReference, WeakDictionary, and WeakKeyDictionary.

KeepAliveWeakReference is a WeakReference inheritor that simple calls KeepAlive every time its target is got. So, you can use only such WeakReference as the reference to the item you want to cache (for example, the background image used in the example of how to use WeakReferences in the MSDN help), and you guarantee that it will not be collected while you are constantly re-rendering the background, even if a collection happens. And, if it stops being used, then it will die after the second collection.

For example, declare a variable like:

var weakReference = new KeepAliveWeakReference<byte[]>(bytes);

where bytes can be an array of many MB. And then, you access it like:

byte[] bytes = weakReference.Target;

Doing this already calls KeepAlive for you. So, if bytes is not null, you can use it safely.

WeakDictionary is effectively a dictionary where values can be collected. It does not use WeakReferences, using the GCHandle directly for performance, but the idea is the same. And, in the Collected event, it removes the values that becomes null.

WeakKeyDictionary allows the keys to be collected. This is useful if you want to "add" fields to existing classes. For example, using extension methods, I add the GetTag and SetTag (simulating the Tag property that I used in Delphi) to any object. But, if I use a dictionary to do this, I will not allow an object with a tag to be collected. Using WeakKeyDictionary, I allow this.

Here is the source code. I simply use a WeakKeyDictionary to store the tag value. I consider the default value to be -1, so if the object does not have a tag, -1 is returned, and setting -1 to the tag value will remove it from the dictionary. The way the dictionary works, while the object is alive, its tag is alive. Just after it dies, the tag in the dictionary dies too.

public static class PropertyExtender
{
    private static WeakKeyDictionary<object, int> fTags = 
            new WeakKeyDictionary<object,int>();

    public static int GetTag(this object obj)
    {
        int result;
        if (fTags.TryGetValue(obj, out result))
            return result;
        return -1;
    }

    public static void SetTag(this object obj, int tagValue)
    {
        if (tagValue == -1)
            fTags.Remove(obj);
        else
            fTags[obj] = tagValue;
    }
}

History

  • 7th April, 2009: Initial post
  • 8th April, 2009: New version of the library and sample

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here