Reputation: 151
I've been reading about this Asynchronous article from MSDN, but I cannot understand the purpose of the Lazy<T>
on the given example.
public class AsyncCache<TKey, TValue>
{
private readonly Func<TKey, Task<TValue>> _valueFactory;
private readonly ConcurrentDictionary<TKey, Lazy<Task<TValue>>> _map;
public AsyncCache(Func<TKey, Task<TValue>> valueFactory)
{
if (valueFactory == null) throw new ArgumentNullException("loader");
_valueFactory = valueFactory;
_map = new ConcurrentDictionary<TKey, Lazy<Task<TValue>>>();
}
public Task<TValue> this[TKey key]
{
get
{
if (key == null) throw new ArgumentNullException("key");
return _map.GetOrAdd(key, toAdd =>
new Lazy<Task<TValue>>(() => _valueFactory(toAdd))).Value;
}
}
}
From what I understand, when you call .Value
of Lazy<T>
then it will call the constructor within. From the example, it is called right away, so why add Lazy<T>
?
Upvotes: 4
Views: 246
Reputation: 79461
Suppose you modified it to not use Lazy<T>
.
public class AsyncCache<TKey, TValue>
{
private readonly Func<TKey, Task<TValue>> _valueFactory;
private readonly ConcurrentDictionary<TKey, Task<TValue>> _map;
public AsyncCache(Func<TKey, Task<TValue>> valueFactory)
{
if (valueFactory == null) throw new ArgumentNullException("loader");
_valueFactory = valueFactory;
_map = new ConcurrentDictionary<TKey, Task<TValue>>();
}
public Task<TValue> this[TKey key]
{
get
{
if (key == null) throw new ArgumentNullException("key");
return _map.GetOrAdd(key, toAdd => _valueFactory(toAdd));
}
}
}
See the remarks in the documentation:
If you call
GetOrAdd
simultaneously on different threads,addValueFactory
may be called multiple times, but its key/value pair might not be added to the dictionary for every call.
So the _valueFactory
might be called multiple times for the same key if multiple accesses to the same key occur simultaneously.
Now how does the use of Lazy<T>
fix the problem? Although multiple Lazy<Task<TValue>>
instances might be created by concurrent calls, only one will ever be returned from GetOrAdd
. So only one will ever have its Value
property accessed. So only one call to _valueFactory
will ever occur per key.
This is of course a desirable feature. If I made an AsyncCache<string, byte[]> cache
that was created with the lambda url => DownloadFile(url)
, I wouldn't want a pile of concurrent requests to cache[myUrl]
to download the file multiple times.
Upvotes: 5
Reputation: 928
The concurrent dictionary may call the create lambda of the GetOrAdd many times, but the value will only be added once. Which will result in the lazy value being created exactly once.
A more complete answer:
Say you have two threads. Both call the GetOrAdd method, both execute the create lambda of the GetOrAdd method. At this point they will both try to add the new key value to the bucket. Only one thread will successfully add the value to the bucket, the other will fail and then execute a get command instead. At which point it will retrieve the value the first thread created. They will both access the value of the same Lazy object and _valueFactory(toAdd) will be exectuted once.
Upvotes: 0