Reputation: 86957
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:
someDateTime
key exists in the dictionary.Lazy<Task<DateTime>>
(which is basically instant)Lazy
instance. (so far, the actual 'expensive' operation hasn't been called, yet.)Value
, which is a Task<DateTime>
.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).
Upvotes: 5
Views: 1269
Reputation: 36361
Because I'm assuming (above) that the value in the key/value is a
Lazy<Task<DateTime>>
Yes, that is true.
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.
await is not a call, it is more like "continue execution when the result is available". Accessing Lazy.Value
will create the task, and this will initiate the call to the GetDataFromSomeExternalDependency
that eventually returns the DateTime. You can await the task however many times you want and get the same result.
Upvotes: 3