There's various posts/answers that say that the .NET/.NET Core's ConcurrentDictionary
GetOrAdd
method is not thread-safe when the Func
delegate is used to calculate the value to insert into the dictionary, if the key didn't already exist.
I'm under the belief that when using the factory method of a ConcurrentDictionary
's GetOrAdd
method, it could be called multiple times "at the same time/in really quick succession" if a number of requests occur at the "same time". This could be wasteful, especially if the call is "expensive". (@panagiotis-kanavos explains this better than I). With this assumption, I'm struggling to understand how some sample code I made, seems to work.
I've created a working sample on .NET Fiddle but I'm stuck trying to understand how it works.
A common recommendation suggestion/idea I've read is to have a Lazy<Task<T>>
value in the ConcurrentDictionary
. The idea is that the Lazy
prevents other calls from executing the underlying method.
The main part of the code which does the heavy lifting is this:
public static async Task<DateTime> GetDateFromCache()
{
var result = await _cache.GetOrAdd("someDateTime", new Lazy<Task<DateTime>>(async () =>
{
// NOTE: i've made this method take 2 seconds to run, each time it's called.
var someData = await GetDataFromSomeExternalDependency();
return DateTime.UtcNow;
})).Value;
return result;
}
This is how I read this:
- Check if
someDateTime
key exists in the dictionary. - If yes, return that. <-- That's a thread-safe atomic action. Yay!
- If no, then here we go ....
- Create an instance of a
Lazy<Task<DateTime>>
(which is basically instant) - Return that
Lazy
instance. (so far, the actual 'expensive' operation hasn't been called, yet.) - Now get the
Value
, which is aTask<DateTime>
. - Now
await
this task .. which finally does the 'expensive' call. It waits 2 seconds .. and then returns the result (some point in Time).
Now this is where I'm all wrong. Because I'm assuming (above) that the value in the key/value is a Lazy<Task<DateTime>>
... which the await
would call each time. If the await
is called, one at a time (because the Lazy
protects other callers from all calling at the same time) then I would have though that the result would a different DateTime
with each independent call.
So can someone please explain where I'm wrong in my thinking, please?
(please refer to the full running code in .NET Fiddler).