2016-10-17 2 views
0

У меня есть private ConcurrentDictionary, который представляет собой простую таблицу поиска некоторых ключей БД.Как сохранить результат метода async в .NET ConcurrentDictionary при вызове GetOrAdd?

Я пытаюсь использовать ConcurrentDictionary так, чтобы он выполнял только один вызов в db, когда запросы 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); 
} 

Любые идеи ?

EDIT:

Обратите внимание мой метод подписи и то, что я возвращаюсь. Лучше ли возвращать int, а не Task<int>, а затем как-то реорганизовать мой вызов db, чтобы все еще быть асинхронным .. но .. лучше?

+0

Что случилось с сохранением задачи в словаре? –

+0

Люди, которые голосуют, чтобы закрыть или downvoting, пожалуйста, объясните, почему, чтобы улучшить Q. –

+1

@StephenCleary Я не был уверен, что это было хорошо или плохо. Это было похоже на объект _heavy_ для хранения, когда результатом будет просто 'int'. –

ответ

0

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 недостаточно, и вам потребуется использовать дополнительную синхронизацию.

+2

Я подозреваю, что это хуже, чем код OP для типичных случаев: в вашей версии предположим, что задача занимает пять секунд, а через три секунды другая задача вызывает «GetOrAddAsync» для того же ключа. В вашем коде это гарантирует запуск новой задачи. В коде OP очень вероятно повторное использование существующей задачи. Да, вы правы, что код OP ничего не гарантирует, но это делает гораздо более вероятным, что я поеду с версией OP. – hvd

+0

@hvd Вы правы. Я работал в предположении, что вы не хотите хранить 'Task' в словаре. – svick

Смежные вопросы