如果我有以下代码:
var dictionary = new ConcurrentDictionary<int, HashSet<string>>();
foreach (var user in users)
{
if (!dictionary.ContainsKey(user.GroupId))
{
dictionary.TryAdd(user.GroupId, new HashSet<string>());
}
dictionary[user.GroupId].Add(user.Id.ToString());
}
因为HashSet是并发字典的value属性,所以将项目添加到HashSet本质上是线程安全的吗?
答案 0 :(得分:5)
不。将容器放入线程安全的容器中不会使内部容器的线程安全。
dictionary[user.GroupId].Add(user.Id.ToString());
在从ConcurrentDictionary中检索HashSet的添加后调用它。如果一次从两个线程中查找此GroupId,则可能会以奇怪的失败模式破坏代码。我看到我的一个队友犯了一个错误,即没有锁定他的布景,而且效果不佳。
这是一个可行的解决方案。我本人会做一些不同的事情,但这更接近您的代码。
if (!dictionary.ContainsKey(user.GroupId)
{
dictionary.TryAdd(user.GroupId, new HashSet<string>());
}
var groups = dictionary[user.GroupId];
lock(groups)
{
groups.Add(user.Id.ToString())
}
答案 1 :(得分:4)
不,集合(字典本身)是线程安全的,而不管您放入其中的内容如何。您有两种选择:
使用AddOrUpdate
作为@TheGeneral提到的内容:
dictionary.AddOrUpdate(user.GroupId, new HashSet<string>(), (k,v) => v.Add(user.Id.ToString());
使用并发集合,例如ConcurrentBag<T>
:
ConcurrentDictionary<int, ConcurrentBag<string>>
无论何时构建词典(如代码中的内容),最好都尽可能少地访问它。想像这样的东西:
var dictionary = new ConcurrentDictionary<int, ConcurrentBag<string>>();
var grouppedUsers = users.GroupBy(u => u.GroupId);
foreach (var group in grouppedUsers)
{
// get the bag from the dictionary or create it if it doesn't exist
var currentBag = dictionary.GetOrAdd(group.Key, new ConcurrentBag<string>());
// load it with the users required
foreach (var user in group)
{
if (!currentBag.Contains(user.Id.ToString())
{
currentBag.Add(user.Id.ToString());
}
}
}
ConcurrentDictionary<int, ConcurrentDictionary<string, string>>
,并关心密钥或内部密钥的值。答案 2 :(得分:1)
像这样使用ConcurrentDictionary
并不是线程安全的
dictionary[user.GroupId].Add(user.Id.ToString());
代替使用
AddOrUpdate(TKey, TValue, Func)
dictionary.AddOrUpdate(user.GroupId, new HashSet<string>(), (k,v) => v.Add(user.Id.ToString());
或者如 Camilo Terevinto 所说,ConcurrentBag
可能是您想要的地方