【问题标题】:How does this ConcurrentDictionary + Lazy<Task<T>> code work?这个 ConcurrentDictionary + Lazy<Task<T>> 代码是如何工作的?
【发布时间】:2021-01-12 07:22:21
【问题描述】:

variousposts/answers 表示 .NET/.NET Core 的 ConcurrentDictionary GetOrAdd 方法在使用 Func 委托计算如果键不存在,则插入字典中的值。

我相信,当使用 ConcurrentDictionaryGetOrAdd 方法的工厂方法时,如果发生多个请求,它可以“同时/非常快速地连续”多次调用同时”。这可能是一种浪费,尤其是在通话“昂贵”的情况下。 (@panagiotis-kanavos 比我解释得更好)。有了这个假设,我很难理解我制作的一些示例代码似乎是如何工作的。

我有 created a working sample on .NET Fiddle,但我一直在努力理解它是如何工作的。

我读过的一个常见的建议建议/想法是在ConcurrentDictionary 中有一个Lazy&lt;Task&lt;T&gt;&gt; 值。这个想法是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&lt;Task&lt;DateTime&gt;&gt; 的实例(基本上是即时的)
  • 返回Lazy 实例。 (到目前为止,还没有调用实际的“昂贵”操作。)
  • 现在获取Value,即Task&lt;DateTime&gt;
  • 现在await 这个任务.. 最终完成了“昂贵”的调用。它等待 2 秒 .. 然后返回结果(某个时间点)。

现在我错了。因为我假设(上面)键/值中的值是 Lazy&lt;Task&lt;DateTime&gt;&gt; ... 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


【解决方案1】:

因为我假设(上面)键/值中的值是Lazy&lt;Task&lt;DateTime&gt;&gt;

是的,确实如此。

await 每次都会调用它。如果一次调用一个 await(因为 Lazy 会保护其他调用者免于同时调用所有调用),那么我会认为每次独立调用的结果都会不同。

await 不是调用,它更像是“当结果可用时继续执行”。访问Lazy.Value 将创建任务,这将启动对最终返回日期时间的GetDataFromSomeExternalDependency 的调用。您可以多次等待任务并获得相同的结果。

【讨论】:

    猜你喜欢
    • 2010-12-13
    • 2013-04-23
    • 2015-04-09
    • 2013-07-24
    • 2014-03-26
    • 2012-11-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多