这个示例代码中的 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
会在返回单个结果之前执行多次。
我在 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
会在返回单个结果之前执行多次。