ConcurrentDictionary.GetOrAdd - 只有当不为空时才添加
ConcurrentDictionary.GetOrAdd - Add only if not null
我正在使用 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,但它看起来不是线程安全的,也不是非常优雅的解决方案。无用的插入和从字典中删除。你知道怎么做吗?
这是一个 hacky 解决方案,我希望有更好的解决方案。如果找不到用户,则使 GetUserFromDb
抛出。这将中止存储到字典中。使 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 User GetUser(int userId)
{
var user = _user.GetOrAddIfNotNull(userId, GetUserFromDb)
}
更新
根据@usr 的评论,可能会出现以下情况:
- 线程 1 执行
GetOrAdd
,将 null
添加到字典并暂停。
- 用户已添加到数据库。
- 线程 2 执行
GetOrAdd
并从字典中检索 null
而不是访问数据库。
- 线程 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;
}
}
}
我正在扩展@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;
}
}
我正在使用 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,但它看起来不是线程安全的,也不是非常优雅的解决方案。无用的插入和从字典中删除。你知道怎么做吗?
这是一个 hacky 解决方案,我希望有更好的解决方案。如果找不到用户,则使 GetUserFromDb
抛出。这将中止存储到字典中。使 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 User GetUser(int userId)
{
var user = _user.GetOrAddIfNotNull(userId, GetUserFromDb)
}
更新
根据@usr 的评论,可能会出现以下情况:
- 线程 1 执行
GetOrAdd
,将null
添加到字典并暂停。 - 用户已添加到数据库。
- 线程 2 执行
GetOrAdd
并从字典中检索null
而不是访问数据库。 - 线程 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;
}
}
}
我正在扩展@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;
}
}