How to store the result of an async method in a .N

2019-08-06 03:00发布

问题:

I have a private ConcurrentDictionary that is a simple lookup table of some DB keys.

I'm trying to leverage the ConcurrentDictionary so that it will only do one call to the db when 2+ requests to the same line of code, are made at the same time. (Which is why i'm using a ConcurrentDictionary.)

How can I do this please?

This is what I was trying to do .. but I think it's storing the Task in the dictionary ... not the result of the 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);
}

Any ideas?

EDIT:

Notice my method signature and what I'm returning. Is it better to return an int and not a Task<int> and then somehow refactor my db call to still be async .. but .. better?

回答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:

If you call GetOrAdd simultaneously on different threads, addValueFactory may be called multiple times, but its key/value pair might not be added to the dictionary for every call.

This can be also seen in the implementation:

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

So, to do about as good a job as GetOrAdd(), you can do something like (input checking omitted):

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));
}

If the requirement to not call the delegate twice at the same time is just a performance optimization, this should be sufficient.

If it's required for correctness of your code, then even GetOrAdd is not sufficient and you will need to use additional synchronization.