Lazy thread-safe delegate for ConcurrentDictionary in C#
Tags: programming, dotnet, csharp, tutorial
Published: 2023-01-26 21:14:16

---
title: Lazy thread-safe delegate for ConcurrentDictionary in C#
published: true
date: 2023-01-26 21:03:32 UTC
tags: programming, dotnet, csharp, tutorial
canonical_url: https://zagosk.in/lazy-thread-safe-delegate-for-concurrentdictionary-in-csharp
---

The `ConcurrentDictionary<TKey, TValue>` type is commonly used as a thread-safe alternative to the `Dictionary<TKey, TValue>` type. While the methods of this type are atomic, it's important to keep in mind that when the `AddOrUpdate` and `GetOrAdd` methods are called from multiple threads, the delegate (`valueFactory` parameter) may be executed more than once.

This is what [official documentation](https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=net-7.0) says about it:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1674423755128/c190f068-e12c-4c3f-b87d-83011fe66bf4.png)

Let's take a look at this example:

```csharp
using System.Collections.Concurrent;

var usersById = new ConcurrentDictionary<int, User>();

// Get or create a user with ID = 1 two times from different threads
var getUserTask1 = Task.Run(() => usersById.GetOrAdd(1, User.GenerateUserWithId));
var getUserTask2 = Task.Run(() => usersById.GetOrAdd(1, User.GenerateUserWithId));

await Task.WhenAll(getUserTask1, getUserTask2);

Console.WriteLine($"User 1: {getUserTask1.Result}");
Console.WriteLine($"User 2: {getUserTask2.Result}");

public record User(int Id, int Age)
{
    private static readonly Random _randomizer = Random.Shared;

    public static User GenerateUserWithId(int id)
    {
        Thread.Sleep(TimeSpan.FromSeconds(1));

        // Generate and return a new user with a given ID and random age.
        var user = new User(id, _randomizer.Next(1, 100));
        Console.WriteLine($"Created a new user: {user}");
        return user;
    }
}

```

Output:

```
Created a new user: User { Id = 1, Age = 91 }
Created a new user: User { Id = 1, Age = 30 }
Instance 1: User { Id = 1, Age = 91 }
Instance 2: User { Id = 1, Age = 91 }

```

As we can see, two `User` objects were created, but only one of them was added to the `ConcurrentDictionary`. While it may be acceptable for the delegate to be called multiple times in some cases, there may be situations where it is necessary to ensure that the delegate is only called once.

## Solution

```csharp
using System.Collections.Concurrent;

//! Replace User with Lazy<User>
var usersById = new ConcurrentDictionary<int, Lazy<User>>();

var getUserTask1 = Task.Run(() => usersById.GetOrAdd(1, x => new Lazy<User>(() => User.GetUserById(x))).Value);
var getUserTask2 = Task.Run(() => usersById.GetOrAdd(1, x => new Lazy<User>(() => User.GetUserById(x))).Value);

await Task.WhenAll(getUserTask1, getUserTask2);

Console.WriteLine($"Instance 1: {getUserTask1.Result}");
Console.WriteLine($"Instance 2: {getUserTask2.Result}");

```

Output:

```
Created a new user: User { Id = 1, Age = 99 }
Instance 1: User { Id = 1, Age = 99 }
Instance 2: User { Id = 1, Age = 99 }

```

We changed two things: (1) the type of the value of our dictionary (`User` became `Lazy<User>`) and (2) made the factory method lazy. So now instead of running the `valueFactory` delegate twice (and generating two users with the same ID), two `Lazy` instances are being created, and just like in the first example, only one of them becomes a member of the `ConcurrentDictionary`. This `Lazy` object will be returned by both calls to the `GetOrAdd` method made from two parallel threads, which, in turn, ensures that the delegate that generates our users will be called only once.

Thank you for reading