【问题标题】:How to store the result of an async method in a .NET ConcurrentDictionary when calling GetOrAdd?调用 GetOrAdd 时如何将异步方法的结果存储在 .NET ConcurrentDictionary 中?
【发布时间】:2016-10-17 02:02:18
【问题描述】:

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

我正在尝试利用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&lt;int&gt; 然后以某种方式重构我的数据库调用仍然是异步的......但是......更好吗?

【问题讨论】:

  • 在字典中存储任务有什么问题?
  • 投票反对或反对的人,请解释原因,以改善 Q。
  • @StephenCleary 我不确定这是好事还是坏事。当结果只是一个int时,感觉就像一个要存储的对象。

标签: c# async-await concurrentdictionary


【解决方案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 也不够,您将需要使用额外的同步。

【讨论】:

  • 我怀疑这比典型情况下的 OP 代码更糟糕:在您的版本中,假设任务需要五秒钟,三秒钟后,另一个任务调用 GetOrAddAsync 获取相同的密钥。在您的代码中,这保证会启动一个新任务。在 OP 的代码中,极有可能重用现有任务。是的,你说得对,OP 的代码不能保证任何事情,但它确实使我更有可能使用 OP 的版本。
  • @hvd 你是对的。我假设您不想将Tasks 存储在字典中。
猜你喜欢
  • 1970-01-01
  • 2019-06-04
  • 2017-02-27
  • 1970-01-01
  • 1970-01-01
  • 2018-09-01
  • 1970-01-01
  • 2016-12-05
  • 2019-03-19
相关资源
最近更新 更多