Skip to main content

Lazy ConcurrentDictionary

· 3 min read

Well that's an interesting title but what does it mean? I assume you know about the .NET System.Collections.Concurrent namespace. It provides several thread-safe collection classes, one of which is ConcurrentDictionary<TKey, TValue>. The standard Dictionary class is not thread-safe and when a reader detects a simultaneous write it throws an InvalidOperation: Collection was modified; enumeration operation may not execute. A ConcurrentDictionary can be read from and written to simultaneously from multiple threads.

This post specifically concerns two methods of the ConcurrentDictionary class: AddOrUpdate and GetOrAdd. Both accept a delegate for generating the value to be updated or added. So what happens when multiple threads call GetOrAdd with the same key? This may occur when you use the dictionary as a simple cache, for example: two threads need the same cached value or add it when it isn't available.

In case two threads call GetOrAdd with the same key and the value factory is a delegate, the delegate is actually run on both threads. This is documented in the remarks section but it may not always be what you want. The operation to get the value could be CPU or I/O intensive so ideally you want to run it just once for a particular dictionary key. In a project I'm currently working on, obtaining the value involves traversing a directory structure looking for a certain file. Something I'd like to do just once.

So this is where the 'lazy' part of this post comes into play. Suppose we have a ConcurrentDictionary<string, string> where the value takes some time to compute. For example:

var dictionary = new ConcurrentDictionary<string, string>();
var value = dictionary.GetOrAdd("key", _ =>
{
// Perform a CPU or I/O intensive operation to obtain the value.
return "value";
});

How to prevent the operation from running twice? The framework offers the Lazy<T> class for that. It can be used for lazy initialization of values but it also offers handy support for our case. First we change the dictionary to have lazy values:

var dictionary = new ConcurrentDictionary<string, Lazy<string>>();

Next, we move the value generator delegate to the lazy instance and specify a LazyThreadSafetyMode of ExecutionAndPublication. This means only one thread is allowed to initialize the value:

var lazy = new Lazy<string>(() =>
{
// Perform a CPU or I/O intensive operation to obtain the value.
return "value";
}, LazyThreadSafetyMode.ExecutionAndPublication);

var lazyValue = dictionary.GetOrAdd("key", lazy);
var value = lazyValue.Value;

What happens here is two things:

  • We add the Lazy<string> instance to the concurrent dictionary in a thread-safe manner.
  • Only the first thread that calls Lazy<T>.Value actually executes the value generator, other threads simply wait for the value to become available.

I wrote this post mainly because I wasn't aware that the ConcurrentDictionary methods that accept a delegate are thread-safe but still allow their delegates to run simultaneously. Maybe you didn't know this either and this post provides you a simple way of preventing this.