这个 ConcurrentDictionary + Lazy<Task<T>> 代码是如何工作的?

Pur*_*ome 5 .net c# asynchronous concurrentdictionary

各种 帖子/答案表示,如果键尚不存在,则ConcurrentDictionary GetOrAdd当使用委托计算要插入字典中的值时,.NET/.NET Core 的方法不是线程安全的。Func

我相信,当使用 aConcurrentDictionary的方法的工厂方法时GetOrAdd,如果“同时”发生多个请求,则可以“同时/快速连续地”多次调用它。这可能会造成浪费,尤其是当呼叫“昂贵”时。(@panagiotis-kanavos 比我解释得更好)。有了这个假设,我正在努力理解我制作的一些示例代码似乎是如何工作的。

我已经在 .NET Fiddle 上创建了一个工作示例,但我一直试图理解它是如何工作的。

普通的推荐我读过的建议/想法是Lazy<Task<T>>ConcurrentDictionary. 这个想法是阻止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;
    }
Run Code Online (Sandbox Code Playgroud)

我是这样读的:

  • 检查someDateTime字典中是否存在 key。
  • 如果是,请返回。<-- 这是一个线程安全的原子操作。耶!
  • 如果没有,那么我们就开始吧......
  • 创建 a 的实例Lazy<Task<DateTime>>(基本上是即时的)
  • 返回该Lazy实例。(到目前为止,尚未调用实际的“昂贵”操作。)
  • 现在得到Value,这是一个Task<DateTime>
  • 现在await这个任务..最终完成了“昂贵”的调用。它等待 2 秒......然后返回结果(某个时间点)。

现在这就是我错的地方。因为我假设(上面)键/值中的值是Lazy<Task<DateTime>>...await每次都会调用。如果await一次调用一个(因为它Lazy可以保护其他调用者同时不被所有调用),那么我会认为DateTime每个独立调用的结果都会不同。

那么有人可以解释一下我的想法哪里错了吗?

(请参考.NET Fiddler中完整的运行代码)。

Jon*_*asH 3

因为我假设(上面)键/值中的值是Lazy<Task<DateTime>>

是的,确实如此。

每次都会调用 wait 。如果一次调用一个等待(因为惰性保护其他调用者不会同时调用所有调用),那么我会认为每次独立调用的结果都会是不同的日期时间。

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