Cache expensive results with ConcurrentDictionary
C#
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
/// <summary>Thread-safe cache that computes each value at most once.</summary>
public sealed class MemoCache<TKey, TValue> where TKey : notnull
{
private readonly ConcurrentDictionary<TKey, Lazy<TValue>> _items = new();
public TValue GetOrAdd(TKey key, Func<TKey, TValue> factory) =>
_items.GetOrAdd(
key,
k => new Lazy<TValue>(() => factory(k), LazyThreadSafetyMode.ExecutionAndPublication))
.Value;
}
public static class Demo
{
public static async Task Main()
{
var calls = 0;
var cache = new MemoCache<int, string>();
var tasks = Enumerable.Range(1, 8).Select(_ => Task.Run(() => cache.GetOrAdd(42, key =>
{
Interlocked.Increment(ref calls);
Thread.Sleep(50); // pretend this is a slow database call
return $"value-{key}";
})));
var results = await Task.WhenAll(tasks);
Console.WriteLine($"{results[0]} computed {calls} time(s)"); // value-42 computed 1 time(s)
}
}