我在MSDN (found here)上找到的使用Task
的示例之一似乎很奇怪。是否有理由在这里使用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<Task<TValue>>
后,立即访问它。如果它被立即访问,那么使用Lazy<T>
只会增加开销,并使这个例子比它需要的更令人困惑。除非我在这里遗漏了什么?
答案 0 :(得分:4)
你是正确的,它被创建然后立即访问,但重要的是要注意 你不总是使用你创建的对象 。
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
调用返回,您将始终使用相同的单个对象,即调用.Value
时,这将使用ExecutionAndPublication
模式,并将阻止对{的任何其他调用{1}}直到.Value
完成执行。
如果我们没有使用_valueFactory
,则Lazt<T>
会在返回单个结果之前多次执行。
答案 1 :(得分:1)
ConcurrentDictionary
不保证您的值工厂只执行一次。这是为了避免在内部锁定下调用用户代码。这可能导致死锁,这是糟糕的API设计。
可以创建多个lazies,但实际上只会实现其中一个,因为字典只会返回其中一个。
这确保了一次性执行的保证。这是一种标准模式。