ConcurrentDictionary GetOrAdd async - PullRequest
       30

ConcurrentDictionary GetOrAdd async

0 голосов
/ 09 января 2019

Я хочу использовать что-то вроде GetOrAdd с ConcurrentDictionary в качестве кэша для веб-службы. Есть ли асинхронная версия этого словаря? GetOrAdd будет делать веб-запрос, используя HttpClient, поэтому было бы неплохо, если бы была версия этого словаря, в которой GetOrAdd был асинхронным.

Чтобы устранить некоторую путаницу, содержимое словаря будет ответом на вызов веб-службы.

ConcurrentDictionary<string, Response> _cache = new ConcurrentDictionary<string, Response>();



var response = _cache.GetOrAdd("id", (x) => { _httpClient.GetAsync(x).GetAwaiter().GetResponse();} )

Ответы [ 2 ]

0 голосов
/ 09 января 2019

GetOrAdd не станет асинхронной операцией, потому что доступ к значению словаря не является длительной операцией.

Однако вы можете просто хранить задачи в словаре, а не материализованный результат. Любой, кому нужны результаты, может дождаться этой задачи.

Однако вам также необходимо убедиться, что операция запускается только один раз, а не несколько раз. Чтобы некоторые операции выполнялись только один раз, а не несколько раз, вам также необходимо добавить Lazy:

ConcurrentDictionary<string, Lazy<Task<Response>>> _cache = new ConcurrentDictionary<string, Lazy<Task<Response>>>();

var response = await _cache.GetOrAdd("id", url => new Lazy<Task<Response>>(_httpClient.GetAsync(url))).Value;
0 голосов
/ 09 января 2019

Метод GetOrAdd не так уж хорош для этой цели. Так как он не гарантирует, что фабрика запускается только один раз, единственная цель, которую он имеет, - это небольшая оптимизация (незначительная, поскольку добавления в любом случае редки) в том, что ей не нужно хешировать и дважды находить правильный сегмент (что может произойти дважды, если вы получаете и устанавливаете с двумя отдельными вызовами).

Я бы посоветовал сначала проверить кеш, если вы не нашли значение в кеше, а затем ввести некую форму критической секции (блокировка, семафор и т. Д.), Перепроверить кеш, если он все еще отсутствует, то получить значение и вставить в кеш.

Это гарантирует, что ваш бэк-магазин будет поражен только один раз; даже если несколько запросов одновременно пропускают кэш, только первый из них фактически извлечет значение, остальные запросы будут ожидать семафор, а затем возвращаться рано, поскольку они повторно проверяют кэш в критической секции.

Код Psuedo (используется SemaphoreSlim со счетчиком 1, поскольку вы можете ожидать его асинхронно):

async Task<TResult> GetAsync(TKey key)
{
    // Try to fetch from catch
    if (cache.TryGetValue(key, out var result)) return result;

    // Get some resource lock here, for example use SemaphoreSlim 
    // which has async wait function:
    await semaphore.WaitAsync();    
    try 
    {
        // Try to fetch from cache again now that we have entered 
        // the critical section
        if (cache.TryGetValue(key, out result)) return result;

        // Fetch data from source (using your HttpClient or whatever), 
        // update your cache and return.
        return cache[key] = await FetchFromSourceAsync(...);
    }
    finally
    {
        semaphore.Release();
    }
}
...