Albert
Albert

Reputation: 151

Purpose of Lazy<T> on this MSDN Example

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

Answers (2)

Timothy Shields
Timothy Shields

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

Will
Will

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

Related Questions