ASP.NET Concurrency and Cache Access
Concurrency must be considered when writing an ASP.NET application. Why it must be considered is because page requests are handled concurrently. I have seen a lot of ASP.NET code that would run fine in a single-thread, but not in a multi-threaded application. In particular, I've seen this often with code that is accessing the application Cache. The cache is thread-safe. How you use it, however, is not always. Here's a example that represents many cases I've seen posted on the web.
1: Random getMyCacheItem()
{
if (HttpRuntime.Cache["myCacheItem"] == null)
{
HttpRuntime.Cache["myCacheItem"] = new Random();
}
return (Random) HttpRuntime.Cache["myCacheItem"];
}
I see two problems with that code. The first problem is the concurrency issue that is the topic of this article. If the cache contains an entry for "myCacheItem", then between line 3 and line 7 it could be deleted explicitly by another thread or by a cache purging event. So whatever is relying on my cache instance of Random is expecting a non-null return, but what it gets is a null return. That null return then causes a NullReferenceException, which the caller wasn't expecting. The second problem is that I'm casting to Random. I'm assuming that everyone on my team (and myself) is not going to throw a non Random item into that cache entry. If someone did, I'm now causing an InvalidCastException to be thrown.
The solution to the first problem is to not check the cache value to see if it is null, but to store the cache value and check the local variable to see if it is null. That means I'm reading from the cache once. If it is null then I'll set the local variable to a new instance and then the cache item to that new instance. Then I'll return the local variable. The solution to the second problem, is to use the as keyword. Please note that as must be used with a reference or nullable type. I recommend storing a nullable type in the cache to avoid the exceptions, i.e. int? instead of int. Here's an updated sample.
1: Random getMyCacheItem()
{
Random retValue = HttpRuntime.Cache["myCacheItem"] as Random;
if (retValue == null)
{
HttpRuntime.Cache["myCacheItem"] = retValue = new Random();
}
return retValue;
}
Wait! There's more, you could also use the null coalescing operator. Here's the code for that.
1: Random getMyCacheItem()
{
return (HttpRuntime.Cache["myCacheItem"] ?? (HttpRuntime.Cache["myCacheItem"] = new Random())) as Random;
}
And since I'm at it here's one more, I see a nice generic method that could be made for this.
1: public static T getOrCreateCacheItem<T>(string keyName) where T : class
{
return (HttpRuntime.Cache[keyName] ?? (HttpRuntime.Cache[keyName] = new T())) as T;
}
Now that's a method worthy of your framework. It's late and I'm leaving two days early for VSLive! 2008, which is now today, so that's all for this post.
I hope this helps.