c# - ConcurrentDictionary.GetOrAdd - 只有在不为空时才添加

标签 c# concurrentdictionary

我正在使用 ConcurrentDictionary 来缓存并行访问的数据,有时新项目可以存储在数据库中,但它们不会加载到缓存中。这就是我使用 GetOrAdd 的原因

public User GetUser(int userId)
{
    return _user.GetOrAdd(userId, GetUserFromDb);        
}

private User GetUserFromDb(int userId)
{
    var user = _unitOfWork.UserRepository.GetById(userId);

    // if user is null, it is stored to dictionary

    return user;
}

但是只有当用户不为空时,我如何才能检查用户是否从数据库中获取并将用户存储到字典中?

可能我可以在 GetOrAdd 之后立即从 ConcurrentDictionary 中删除 null,但它看起来不是线程安全的,也不是非常优雅的解决方案。无用的插入和从字典中删除。你知道怎么做吗?

最佳答案

public User GetUser(int userId)
{
    var user = _user.GetOrAdd(userId, GetUserFromDb);
    if (user == null) _user.TryRemove(userId, out user);    
}

您还可以将其包装到扩展方法中:

public static TValue GetOrAddIfNotNull<TKey, TValue>(
    this ConcurrentDictionary<TKey, TValue> dictionary,
    TKey key, 
    Func<TKey, TValue> valueFactory) where TValue : class
{
    var value = dictionary.GetOrAdd(key, valueFactory);
    if (value == null) dictionary.TryRemove(key, out value);
    return value;
}

那么您的代码将如下所示:

public User GetUser(int userId)
{
    var user = _user.GetOrAddIfNotNull(userId, GetUserFromDb)   
}

更新

根据@usr 的评论,可能会出现以下情况:

  1. 线程 1 执行 GetOrAdd,将 null 添加到字典并暂停。
  2. 用户被添加到数据库中。
  3. 线程 2 执行 GetOrAdd 并从字典中检索 null 而不是访问数据库。
  4. 线程 1 和线程 2 执行 TryRemove 并从字典中删除记录。

此时,线程 2 将获取 null 而不是访问数据库并获取用户记录。如果这种边缘情况对您很重要,并且您仍想使用 ConcurrentDictionary,那么您可以在扩展方法中使用 lock:

public static class ConcurrentDictionaryExtensions
{
    private static readonly object myLock = new object();

    public static TValue GetOrAddIfNotNull<TKey, TValue>(
        this ConcurrentDictionary<TKey, TValue> dictionary,
        TKey key, 
        Func<TKey, TValue> valueFactory) where TValue : class
    {
        lock (myLock)
        {
            var value = dictionary.GetOrAdd(key, valueFactory);
            if (value == null) dictionary.TryRemove(key, out value);
            return value;
        }
    }
}

关于c# - ConcurrentDictionary.GetOrAdd - 只有在不为空时才添加,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31628768/

相关文章:

c# - 类型必须是不可为空的值

c# - 我如何在 Parallel.ForEach 期间添加或更新此 .NET 集合?

c# - 变量命名讨论 : C# vs JavaScript?

c# - 带有 where 子句的 Foreach?

c# - .NET ConcurrentDictionary 初始容量设置为任意质数,而不是 MSDN 示例文档中的预期容量。为什么?

c# - Faster KV vs dictionary vs concurrent dictionary 仅更新字典值时使用哪一个

c# - 如何在 microsoft.clearscript.v8 中的 jsondata 中使用 linq

c# - 将 Stream 数据映射到 C# 中的数据结构

c# - 如何创建打开本地文件的链接?

c# - 使用 Linq 获取这些值后访问 ConcurrentDictionary 值是否线程安全