懒惰的目的<;T>;在这个MSDN示例中

本文关键字:MSDN lt gt | 更新日期: 2023-09-27 18:27:11

我一直在MSDN上阅读这篇异步文章,但我无法理解Lazy<T>在给定示例中的用途。

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;
        }
    }
}

据我所知,当你调用Lazy<T>.Value时,它会调用里面的构造函数。从这个例子中,它被立即调用,那么为什么要添加Lazy<T>呢?

懒惰的目的<;T>;在这个MSDN示例中

假设您将其修改为不使用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));
        }
    }
}

请参阅文档中的备注:

如果在不同的线程上同时调用GetOrAdd,则可能会多次调用addValueFactory,但可能不会在每次调用时将其键/值对添加到字典中。

因此,如果同时发生对同一密钥的多次访问,则_valueFactory可能会针对同一密钥被多次调用。

现在使用Lazy<T>是如何解决问题的呢?尽管并发调用可能会创建多个Lazy<Task<TValue>>实例,但GetOrAdd只会返回一个实例。因此,只有一个会访问其Value属性。因此,每个密钥只会发生一次对_valueFactory的调用。

这当然是一个可取的特点。如果我制作了一个使用lambda url => DownloadFile(url)创建的AsyncCache<string, byte[]> cache,我就不希望有一堆对cache[myUrl]的并发请求多次下载该文件。

并发字典可能会多次调用GetOrAdd的create lambda,但该值只会添加一次。这将导致懒惰值只创建一次。

更完整的答案:

假设你有两条线索。两者都调用了GetOrAdd方法,都执行了GetOrAdd方法的create lambda。此时,他们都将尝试将新的键值添加到bucket中。只有一个线程会成功地将值添加到bucket中,另一个线程将失败,然后执行get命令。此时,它将检索第一个线程创建的值。它们都将访问同一个Lazy对象的值,并且_valueFactory(toAdd)将被执行一次。