The dictionary itself won't get corrupted, but the valueFactory can run several times concurrently (only one result is kept).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // ====================================================================== // Q: ConcurrentDictionary: is GetOrAdd really thread-safe? // // The dictionary itself won't get corrupted, but the valueFactory can run // several times concurrently (only one result is kept). If the factory is // expensive or has side effects (e.g. a token request), wrap it in Lazy, as // shown in the code. // - A failed Task stays cached. On error, call TryRemove, otherwise the error // "sticks" forever. // - dict[key]++ on a ConcurrentDictionary is not atomic. Use AddOrUpdate(key, 1, // (_, v) => v + 1). // - For a cache with expiration: HybridCache, which also has stampede protection // (Q42). // ====================================================================== private readonly ConcurrentDictionary<string, Lazy<Task<ErpToken>>> _tokens = new(); public Task<ErpToken> GetTokenAsync(string tenant) => _tokens.GetOrAdd(tenant, t => new Lazy<Task<ErpToken>>(() => FetchTokenAsync(t))).Value; // Lazy (default: ExecutionAndPublication): only one FetchTokenAsync per tenant |