ConcurrentDictionary.GetOrAdd - 仅在不为空时添加

问题描述 投票:0回答:3

我使用

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

但是我如何检查用户是否从数据库获取并仅在用户不为空时将用户存储到字典中?

也许我可以在

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

c# concurrency thread-safety concurrentdictionary
3个回答
5
投票

这是一个 hacky 解决方案,我希望有更好的解决方案。如果未找到用户,则抛出

GetUserFromDb
。这会中止存储到字典中。让
GetUser
捕获异常。这是使用异常来控制流,这不太好。


3
投票
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;
        }
    }
}

2
投票

我正在扩展 @NikolaiSamteladze 解决方案以包含双重检查锁定,以便其他线程可以在字典更新后跳过获取锁定

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
    {
        TValue value;
        if (!dictionary.TryGetValue(key, out value))
        {
            lock (myLock)
            {
                value = dictionary.GetOrAdd(key, valueFactory);
                if (value == null) dictionary.TryRemove(key, out value);
            } 
        }
        return value;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.