这个示例代码中的 Lazy<T> 有什么用吗?

Is there any purpose to Lazy<T> in this sample code?

我在 MSDN (found here) 上找到的使用 Task 的示例之一似乎很奇怪。这里使用Lazy<T>class有什么理由吗?

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<Task<TValue>> 创建,立即访问它。如果它被立即访问,那么使用 Lazy<T> 只会增加开销,并且使这个示例比它需要的更混乱。除非我在这里遗漏了什么?

ConcurrentDictionary 不保证您的值工厂只执行一次。这是为了避免在内部锁下调用用户代码。这可能会导致死锁,这是糟糕的 API 设计。

可以创建多个懒惰,但实际上只有一个会被具体化,因为字典只会 returns 其中一个。

这确保了一次性执行。这是标准模式。

你是正确的,它被创建然后立即访问,但重要的是要注意你并不总是使用你创建的对象.

Dictionary 的 GetOrAdd 函数就像 Lazy<T>LazyThreadSafetyMode.PublicationOnly 一样,这意味着您作为工厂函数传入的委托可能会执行多次,但只有第一个完成的委托才会执行返回给所有调用者。

Lazy<T>的默认行为是LazyThreadSafetyMode.ExecutionAndPublication,这意味着第一个调用工厂函数的人将获得锁,任何其他调用者必须等到工厂函数完成才能继续。

如果我们重新格式化 get 方法,它会变得更清晰一些。

public Task<TValue> this[TKey key]
{
    get
    {
        if (key == null) 
           throw new ArgumentNullException("key");
        var cacheItem = _map.GetOrAdd(key, toAdd => 
                             new Lazy<Task<TValue>>(() => _valueFactory(toAdd)));
        return cacheItem.Value;
    }
}

因此,如果两个线程都同时调用 this[Tkey key],并且它们都到达 GetOrAdd,但在字典中没有值,并且传入的键是 new Lazy<Task<TValue>>(() => _valueFactory(toAdd)) 将执行两次,但是只有第一个完成的才会返回给两次调用。这没什么大不了的,因为 _valueFactory 还没有执行,这是昂贵的部分,我们所做的只是制作一个新的 Lasy<T>.

一旦 GetOrAdd 调用 returns,您将始终使用同一个对象,即调用 .Value 时,它使用 ExecutionAndPublication 模式并将阻止对 .Value 的任何其他调用,直到 _valueFactory 完成执行。

如果我们不使用 Lazt<T>,那么 _valueFactory 会在返回单个结果之前执行多次。