ConcurrentDictionary<TKey,TValue> used with Lazy<T>
Posted
by Reed
on Reed Copsey
See other posts from Reed Copsey
or by Reed
Published on Sun, 16 Jan 2011 21:04:03 +0000
Indexed on
2011/01/16
21:56 UTC
Read the original article
Hit count: 273
In a recent thread on the MSDN forum for the TPL, Stephen Toub suggested mixing ConcurrentDictionary<T,U> with Lazy<T>. This provides a fantastic model for creating a thread safe dictionary of values where the construction of the value type is expensive. This is an incredibly useful pattern for many operations, such as value caches.
The ConcurrentDictionary<TKey, TValue> class was added in .NET 4, and provides a thread-safe, lock free collection of key value pairs. While this is a fantastic replacement for Dictionary<TKey, TValue>, it has a potential flaw when used with values where construction of the value class is expensive.
The typical way this is used is to call a method such as GetOrAdd to fetch or add a value to the dictionary. It handles all of the thread safety for you, but as a result, if two threads call this simultaneously, two instances of TValue can easily be constructed.
If TValue is very expensive to construct, or worse, has side effects if constructed too often, this is less than desirable. While you can easily work around this with locking, Stephen Toub provided a very clever alternative – using Lazy<TValue> as the value in the dictionary instead.
This looks like the following. Instead of calling:
MyValue value = dictionary.GetOrAdd( key, () => new MyValue(key));
We would instead use a ConcurrentDictionary<TKey, Lazy<TValue>>, and write:
MyValue value = dictionary.GetOrAdd( key, () => new Lazy<MyValue>( () => new MyValue(key))) .Value;
This simple change dramatically changes how the operation works. Now, if two threads call this simultaneously, instead of constructing two MyValue instances, we construct two Lazy<MyValue> instances.
However, the Lazy<T> class is very cheap to construct. Unlike “MyValue”, we can safely afford to construct this twice and “throw away” one of the instances.
We then call Lazy<T>.Value at the end to fetch our “MyValue” instance. At this point, GetOrAdd will always return the same instance of Lazy<MyValue>. Since Lazy<T> doesn’t construct the MyValue instance until requested, the actual MyClass instance returned is only constructed once.
© Reed Copsey or respective owner