C# ConcurrentDictionary.GetOrAdd-仅当不为null时添加
我使用ConcurrentDictionary以并行访问的方式缓存数据,有时新项目可以存储在db中,而不会加载到缓存中。这就是我使用GetOrAdd的原因C# ConcurrentDictionary.GetOrAdd-仅当不为null时添加,c#,concurrentdictionary,C#,Concurrentdictionary,我使用ConcurrentDictionary以并行访问的方式缓存数据,有时新项目可以存储在db中,而不会加载到缓存中。这就是我使用GetOrAdd的原因 public User GetUser(int userId) { return _user.GetOrAdd(userId, GetUserFromDb); } private User GetUserFromDb(int userId) { var user = _unitOfWork.UserRepos
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;
}
但我如何检查用户是否是从数据库中获取的,并且仅当用户不为null时才将用户存储到字典中呢
可能我可以在GetOrAdd之后立即从ConcurrentDictionary中删除null,但它看起来不太线程安全,而且不是非常优雅的解决方案。从字典中插入和删除无用的内容。你知道怎么做吗?这里有一个简单的解决方案,我希望有更好的办法。如果找不到用户,则抛出
GetUserFromDb
throw。这将中止存储到字典中。使GetUser
捕获异常。这是对控制流使用异常,这不好
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 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;
}
}
}
更新
根据@usr评论,可能存在以下情况:
GetOrAdd
,将null
添加到字典中并暂停GetOrAdd
并从字典中检索null
,而不是访问数据库TryRemove
并从字典中删除记录null
,而不是命中数据库并获取用户记录。如果此边缘大小写对您很重要,并且您仍然希望使用ConcurrentDictionary
,那么您可以在扩展方法中使用lock
:
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 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;
}
}
}
公共静态类ConcurrentDictionaryExtensions
{
私有静态只读对象myLock=new object();
公共静态TValue GetOrAddifyNotNull(
这本词典,
TKey键,
Func valueFactory),其中TValue:class
{
锁(myLock)
{
var value=dictionary.GetOrAdd(key,valueFactory);
if(value==null)dictionary.TryRemove(key,out值);
返回值;
}
}
}
我正在扩展@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;
}
}
公共静态类ConcurrentDictionaryExtensions
{
私有静态只读对象myLock=new object();
公共静态TValue GetOrAddifyNotNull(
这本词典,
TKey键,
Func valueFactory),其中TValue:class
{
t价值;
if(!dictionary.TryGetValue(键,输出值))
{
锁(myLock)
{
value=dictionary.GetOrAdd(key,valueFactory);
if(value==null)dictionary.TryRemove(key,out值);
}
}
返回值;
}
}
如果GetOrAdd
返回null,则可以TryRemove
删除添加的值。但这不是一个很好的解决方案。如果数据库中的查找返回null,用户是否可以稍后出现在数据库中,这是您想要处理的吗?如果没有,那么用户ID存储在字典中又有什么关系呢?如果找不到,下次请求该用户ID时,字典已缓存该用户ID不存在,因此不需要询问数据库。@y0io您最终使用了什么?@Nikolai Samteladze I稍后将返回此问题,但使用锁定扩展方法的解决方案可能会获胜;)非常感谢谢谢,但是它是线程安全的吗?假设在GetOrAdd之后,其他进程将新用户添加到字典中,然后TryRemove删除现有用户。这种情况发生的可能性很小,但也可能发生。从某种意义上讲,这是一种快速性,即两个线程可能运行GetOrAddifyNotNull,当用户不存在时,只有一个线程可能会访问数据库。听起来像是良性的种族@y0io是的,也是这样。@y0io不确定另一个进程如何将用户添加到字典中,因为在执行GetOrAdd
之后,已经有一个用户具有该userId
。你能解释一下吗@usr我同意第二个线程可能不会命中数据库。但这假设用户是在第一个线程上的GetOrAdd
和TryRemove
之间添加到数据库的。问题是您是否需要这种精度,以及不从数据库检索此用户的连续性是什么。它不再是具有此全局锁的并发字典。OK。ConcurrentDictionary允许对独立键执行并行操作。现在,有一个全局锁。一次只能有一个线程访问字典。这是安全的,但不可扩展。不再使用CD了。字典现在在这种模式下会更合适。是的,它应该可以工作,但我希望它一定有更好的解决方案。我已经给出了+1,但我自己的(有限)测试表明,它不会带来很大的速度提升。@辩手如果你大部分时间都在获取值而不是添加值,那么它确实会带来很大的不同,因为如果TryGetValue返回值,它将跳过锁获取。此设计模式专门用于此目的。