首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ConcurrentDictionary GetOrAdd异步

ConcurrentDictionary GetOrAdd异步
EN

Stack Overflow用户
提问于 2019-01-10 04:16:04
回答 5查看 9.5K关注 0票数 27

我想使用带有ConcurrentDictionaryGetOrAdd之类的东西作为use服务的缓存。这个字典有异步版本吗?GetOrAdd将使用HttpClient发出web请求,所以如果这个字典有一个版本的GetOrAdd是异步的,那就太好了。

为了澄清一些混淆,字典的内容将是对the服务的调用的响应。

代码语言:javascript
复制
ConcurrentDictionary<string, Response> _cache
    = new ConcurrentDictionary<string, Response>();

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

回答 5

Stack Overflow用户

发布于 2019-01-10 04:53:25

GetOrAdd不会成为异步操作,因为访问字典的值不是一个长时间运行的操作。

但是,您可以做的只是将任务存储在字典中,而不是物化的结果。然后,任何需要结果的人都可以等待该任务。

但是,您还需要确保操作只启动一次,而不是多次启动。要确保某些操作只运行一次,而不是多次运行,还需要添加Lazy

代码语言:javascript
复制
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;
票数 25
EN

Stack Overflow用户

发布于 2019-01-10 04:37:04

对于此目的,使用GetOrAdd方法并不是很好。因为它不能保证工厂只运行一次,所以它的唯一目的是一个小的优化(小的,因为添加无论如何都是很少的),因为它不需要散列和找到正确的存储桶两次(这将发生两次,如果你用两个单独的调用获取和设置)。

我建议您首先检查缓存,如果在缓存中没有找到值,则输入某种形式的临界区(锁、信号量等),重新检查缓存,如果仍未找到,则获取该值并插入到缓存中。

这确保后备存储只被命中一次;即使多个请求同时获得缓存未命中,也只有第一个请求会实际获取该值,其他请求将等待信号量,然后提前返回,因为它们会重新检查临界区中的缓存。

Psuedo代码(使用计数为1的SemaphoreSlim,因为您可以异步等待它):

代码语言:javascript
复制
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();
    }
}
票数 10
EN

Stack Overflow用户

发布于 2020-12-12 20:39:40

尝试此扩展方法:

代码语言:javascript
复制
/// <summary>
/// Adds a key/value pair to the <see cref="ConcurrentDictionary{TKey, TValue}"/> by using the specified function 
/// if the key does not already exist. Returns the new value, or the existing value if the key exists.
/// </summary>
public static async Task<TResult> GetOrAddAsync<TKey,TResult>(
    this ConcurrentDictionary<TKey,TResult> dict,
    TKey key, Func<TKey,Task<TResult>> asyncValueFactory)
{
    if (dict.TryGetValue(key, out TResult resultingValue))
    {
        return resultingValue;
    }
    var newValue = await asyncValueFactory(key);
    return dict.GetOrAdd(key, newValue);
}

您使用await dict.GetOrAddAsync(key,async key=>await something(key))而不是dict.GetOrAdd(key,key=>something(key))。显然,在这种情况下,您只需将其编写为await dict.GetOrAddAsync(key,something),但我想说明一下。

有关维持运作秩序的疑虑,我有以下观察所得:

如果你观察它的实现方式,使用普通GetOrAdd的

  1. 也会得到同样的效果。我确实使用了相同的代码,并使其适用于异步。参考资料:

在锁的外部调用valueFactory委托,以避免在锁下执行未知代码时可能出现的问题。因此,对于ConcurrentDictionary类上的所有其他操作,GetOrAdd不是原子的

  1. SyncRoot在ConcurrentDictionary中不受支持,它们使用内部锁定机制,因此无法对其进行锁定。但是,使用您自己的锁定机制仅适用于此扩展方法。如果您使用另一个流(例如使用GetOrAdd ),您将面临相同的问题。
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54117652

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档