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>
?
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.