Here is a (greatly simplified) piece of code that has been happily running on 13 production servers for one year.
The above code has a flaw. We can’t depend on a cached object still being in the cache when we are ready to use it. The code assumes it can place an object in the cache and (almost) immediately pull it back out. Even with the flaw, the code never caused a problem in production environments that log all exceptions.
The weird part is that the data.ToString() line occasionally throws a NullReferenceException on one (and only one) developer machine. How odd that the code fails on a developer machine serving a single request at a time. It’s good the weirdness occurred, because it pointed out a problem. The fix is to return a reference from the initialize cache method, and not to depend on the ASP.NET cache to keep the object alive.
Comments
It is a race condition, but let's just try this code:
1: Cache["DataObject"] = new DataObject();
2: DataObject data;
3: data = Cache["DataObject"] as DataObject;
4: data.ToString();
A lot can happen between line 1 and line 3. It's theoretically possible that another thread might be caching lots of objects and bump our DataObject out of the cache.
private DataObject InitializeCache()
{
DataObject data = new DataObject();
Cache["DataObject"] = data;
return data
}
private void DoWork()
{
DataObject data = Cache["DataObject"]
as DataObject;
if (data == null)
{
data = CreateAndCacheData();
}
data.ToString();
}
private DataObject CreateAndCacheData()
{
DataObject result = new DataObject();
Cache["DataObject"] = result;
return result;
}
The original code was returning a reference indirectly through the Cache, but the Cache doesn't guarantee it will hold onto an object forever, and a few nanoseconds is a long time on a computer.
A better version returns the reference directly to the client.
What good is a cache that doesn't, well, cache? I think of the cache as a bank. As long as I go to the bank during business hours (e.g. during the running time of the application), I should be able to withdraw any money I've put in there.
Or are you saying that other threads could remove/invalidate your cached object in between the putting and the getting in the above example, resulting in an exception. If that's what you mean, then a lock statement would go a little ways towards solving that problem I think.
That will at least help avoid the case where your item gets evicted from the cache to make room for other data. It will only be removed based on the rules you defined (a time limit, or a dependency). That makes the cache more predictable.
Of course you don't want to use that setting in every case, because the Cache should be allowed to evict most data when it needs the memory.