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 WeakReference
s 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 WeakReference
s, 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