调用GetOrAdd时如何在.NET ConcurrentDictionary中存储异步方法的结果?

时间:2016-10-17 02:02:19

标签: c# async-await concurrentdictionary

我有一个private ConcurrentDictionary,它是一些DB键的简单查找表。

我正在尝试利用ConcurrentDictionary,这样当同一时间对同一行代码发出2次以上请求时,它只会对数据库进行一次调用。 (这就是我使用ConcurrentDictionary的原因。)

我该怎么办呢?

这就是我尝试做的事情..但我认为它将Task存储在字典中...而不是结果的任务....

private readonly ConcurrentDictionary<string, Task<int>> _myKeys = new ConcurrentDictionary<string, Task<int>>();

...

private async Task<int> DoStuffAsync(string key)
{
   // do stuff here.

   return await _myKeys.GetOrAdd(key,
                                 async k => await _db.GetId(k)
                                                     .ConfigureAwait(false))
                       .ConfigureAwait(false);
}

有什么想法吗?

编辑:

注意我的方法签名以及我要返回的内容。返回int而不是Task<int>然后以某种方式重构我的数据库调用仍然是异步的...但更好吗?

1 个答案:

答案 0 :(得分:1)

GetOrAdd does not guarantee that the delegate will be called only once when it's called from multiple threads at the same time with the same value:

  

如果您在不同的线程上同时调用GetOrAdd,可能会多次调用 addValueFactory ,但每次调用时,它的键/值对可能不会添加到字典中。

这也可以在the implementation中找到:

TValue resultingValue;
if (TryGetValue(key, out resultingValue))
{
    return resultingValue;
}
TryAddInternal(key, valueFactory(key), false, true, out resultingValue);
return resultingValue;

所以,要做与GetOrAdd()一样好的工作,你可以做一些事情(输入检查省略):

public static async Task<TValue> GetOrAddAsync<TKey, TValue>(
    this ConcurrentDictionary<TKey, TValue> dictionary,
    TKey key, Func<TKey, Task<TValue>> valueFactory)
{
    TValue resultingValue;
    if (dictionary.TryGetValue(key, out resultingValue))
    {
        return resultingValue;
    }
    return dictionary.GetOrAdd(key, await valueFactory(key));
}

如果不同时调用委托两次的要求只是性能优化,那就足够了。

如果您的代码正确无误,那么即使GetOrAdd也不够,您还需要使用其他同步。