Endjin - Home

Using Lazy and ConcurrentDictionary to ensure a thread-safe, run-once, lazy-loaded collection

by Mike Larah


Since .NET 4.0, the ConcurrentDictionary<TKey, TValue> collection type has been available as a way of ensuring thread-safe access to a collection of key/value pairs, using fine grained locking. The collection also has AddOrUpdate and GetOrAdd methods that accept delegate value factories, so that you can lazily load your values only when you need to initialize them.

The fine grained locking doesn’t apply to these methods that accept a delegate, because trying to lock around an unknown method could potentially cause issues, and as such the code in them is not guaranteed to be atomic. In this case, if multiple threads request the same key/value pair at once, the method may run more than once, and all but one of the results will be thrown away, guaranteeing your value result is the same object across all threads.

However, you may find that you want use the GetOrAdd with delegate overload (to benefit from the lazy initialization and thread-safe result) but also want to ensure that the delegate method only runs once. For example, your delegate method may cause side-effects, or consumes a resource (like network bandwidth) that you want to minimise the impact of.

In this case, you can use another type available since .NET 4.0 – Lazy<T>. You can wrap up a type such as Lazy<SomeBigObject> with a value factory delegate, and the delegate method will only be run when you call the Value property of the Lazy object.

Additionally, you can also specify a LazyThreadSafetyMode to the constructor, the default value of which is ExecutionAndPublication that ensures only one thread can initialise the value, which is exactly what we want. So we can combine ConcurrentDictionary and Lazy to get the behaviour we want. I created a LazyConcurrentDictionary to wrap up the GetOrAdd method:

Now what happens is that if multiple concurrent threads try to call GetOrAdd with the same key at once, multiple Lazy objects may be created but these are cheap, and all but one will be thrown away. The return Lazy object will be the same across all threads, and the first one to call the Value property will run the expensive delegate method, whilst the other threads are locked, waiting for the result.

This ensures that the values in the collection are lazy-loaded but they are guaranteed to only be initialized once.

Example is available to look at over at DotNetFiddle. If you look at the console output, you will see the ConcurrentDictionary executing it’s delegate method at least once and the LazyConcurrentDictionary just running it once (you might have to run it a few times to see the regular ConcurrentDictionary execute the method multiple times, but the LazyConcurrentDictionary will always only ever run it once).

About the author

Mike is a Software Engineer at endjin with 4 years experience in solving technology problems for clients. He is a certified Microsoft Cloud Platform developer, and has experience building solutions utilising much of the Azure ecosystem.