I am trying to use ConcurrentDictionary<TKey,Lazy<T>> to get a token and cache it in-memory. The GetToken() method contains logic to read existing tokens from database, check for token expiry and make an HTTP call to refresh the token if the existing token has expired.
public static class TokenCache
{
private static ConcurrentDictionary<int, Lazy<Token>> _tokens = new
ConcurrentDictionary<int, Lazy<Token>>();
private static int dbCount = 0;
public static Token GetToken(int id)
{
// Get the lazy token object
Lazy<Token> lazyToken= _tokens.GetOrAdd(id, key => new Lazy<Token>(() =>
{
var token=GetTokenFromDB(id);
Interlocked.Increment(ref dbCount);
Console.WriteLine("GetOrAdd " + dbCount);
if ((token.ExpiryDate - DateTime.UtcNow).TotalMinutes <= 5)
{
_tokens.TryRemove(id, out _);
return RefreshToken(id, cred);
}
return token;
}));
// Access the token
return lazyToken.Value;
}
}
Parallel.For(1, 100, _ => TokenCache.GetToken(555));
My understanding is if two threads simultaneously call GetToken() and then the GetOrAdd() method, it will create two Lazy<Token> objects but at this point no database call and HTTP call is made yet. The db calls (and token refresh HTTP request) are invoked when we call .Value and thus we will be having a single db call and token refresh even when there are multiple threads. However, when I tested this, I am seeing that the delegate inside Lazy<Token> is being invoked 5-7 times when about 100 threads are run parallelly.
Shouldn't the delegate for Lazy<T> be invoked only once?