【发布时间】:2021-01-12 07:22:21
【问题描述】:
variousposts/answers 表示 .NET/.NET Core 的 ConcurrentDictionary GetOrAdd 方法在使用 Func 委托计算如果键不存在,则插入字典中的值。
我相信,当使用 ConcurrentDictionary 的 GetOrAdd 方法的工厂方法时,如果发生多个请求,它可以“同时/非常快速地连续”多次调用同时”。这可能是一种浪费,尤其是在通话“昂贵”的情况下。 (@panagiotis-kanavos 比我解释得更好)。有了这个假设,我很难理解我制作的一些示例代码似乎是如何工作的。
我有 created a working sample on .NET Fiddle,但我一直在努力理解它是如何工作的。
我读过的一个常见的建议建议/想法是在ConcurrentDictionary 中有一个Lazy<Task<T>> 值。这个想法是Lazy 阻止其他调用执行底层方法。
完成繁重工作的代码的主要部分是这样的:
public static async Task<DateTime> GetDateFromCache()
{
var result = await _cache.GetOrAdd("someDateTime", new Lazy<Task<DateTime>>(async () =>
{
// NOTE: i've made this method take 2 seconds to run, each time it's called.
var someData = await GetDataFromSomeExternalDependency();
return DateTime.UtcNow;
})).Value;
return result;
}
这就是我的阅读方式:
- 检查字典中是否存在
someDateTime键。 - 如果是,请返回。
- 如果没有,那么我们开始......
- 创建
Lazy<Task<DateTime>>的实例(基本上是即时的) - 返回
Lazy实例。 (到目前为止,还没有调用实际的“昂贵”操作。) - 现在获取
Value,即Task<DateTime>。 - 现在
await这个任务.. 最终完成了“昂贵”的调用。它等待 2 秒 .. 然后返回结果(某个时间点)。
现在我错了。因为我假设(上面)键/值中的值是 Lazy<Task<DateTime>> ... await 每次都会调用它。如果一次调用await(因为Lazy 保护其他调用者免受同时调用),那么我会认为每次独立调用的结果都会不同DateTime。
那么有人可以解释一下我的想法哪里错了吗?
(请参考.NET Fiddler中完整的运行代码)。
【问题讨论】:
-
ConcurrentDictionary GetOrAdd method is not thread-safe when根本不是那些答案所说的 -
他们说的是,工厂方法虽然是线程安全的,但如果同时调用多次,则不能保证只产生一个结果。如果发生这种情况,将只使用其中一个值
-
which the await would call each time并非如此。 Task 是一个已经激活的promise,而不是一个函数指针。调用await不会运行promise,它会等待它产生结果。多次调用await不会再次运行任务,会返回第一个结果 -
至于
Lazy,懒惰评估并不真正需要它——当您调用GetDateFromCache时,您希望现在得到答案。ConcurrentDictionary没有异步工厂方法,所以Lazy用于将异步工厂方法转换为同步对象。本质上,它同步了可能的并发工厂调用 -
(because the Lazy protects other callers from all calling at the same time) ... with each independent call.Lazy 确保它只被成功调用一次(默认情况下确保在给定的时间点只发生一次尝试)。第一次运行成功,因此每个后续调用甚至都不会发生。询问已完成任务的Result不会做任何事情。它只是再次提供相同的价值。如果它没有完成(飞行中),它只会阻塞,直到它准备好。这与dotnetfiddle.net/SS3NJH 一致。
标签: c# .net asynchronous concurrentdictionary