如何将一个新元素添加到ConcurrentDictionary中作为HashSet的值?

3
我有一个ConcurrentDictionary,key是一个long类型,value是一个int类型的hashset。如果key不存在于字典中,我想添加一个新的hashset并插入第一个元素。如果key已存在,则将新元素添加到现有的字典中。
我正在尝试如下操作:
ConcurrentDictionary<long, HashSet<int>> myDic = new ConcurrentDictionary<long, HashSet<int>>();
int myElement = 1;
myDic.AddOrUpdate(1, new Hashset<int>(){myFirstElement},
(key, actualValue) => actualValue.Add(myElement));

这段代码的问题在于第三个参数,因为 .Add() 方法返回一个布尔值,而 AddOrUpdate 需要一个哈希集。前两个参数是正确的。因此我的问题是如何以线程安全的方式向哈希集中添加新元素并避免重复项(这就是我使用哈希集作为值的原因)。哈希集的问题在于它不是线程安全的,如果我先获取它,然后再添加新元素,我就在字典之外进行操作,可能会出现问题。谢谢。
2个回答

3
为了解决编译错误,您可以执行以下操作:
myDic.AddOrUpdate(1, new HashSet<int>() { myFirstElement },
    (key, actualValue) => {
        actualValue.Add(myFirstElement);
        return actualValue;
    });

但是,这种方式不是线程安全的,因为"update"函数没有在任何锁内运行,所以你可能会从多个线程中向不安全的HashSet添加内容。这可能导致(例如)丢失值(所以你向HashSet添加了1000个项目,但最终只有970个项目)。在AddOrUpdate中,更新函数不应该有任何副作用,但这里确实存在副作用。
你可以通过在向HashSet添加值时自己加锁来解决这个问题:
myDic.AddOrUpdate(1, new HashSet<int>() { myFirstElement },
    (key, actualValue) => {
        lock (actualValue) {
            actualValue.Add(myFirstElement);
            return actualValue;
        }
    });

但问题是,为什么您首先要使用无锁结构(ConcurrentDictionary)?此外,任何其他代码都可以从字典中获取HashSet并在没有任何锁定的情况下向其中添加值,使整个过程变得无用。因此,如果出于某种原因您决定采用这种方式,则必须确保所有代码在访问该字典中的HashSet时进行锁定。

与其如此,不如直接使用并发集合而不是HashSet。据我所知,没有ConcurrentHashSet,但您可以使用具有虚拟键的另一个ConcurrentDictionary作为替代品(或在互联网上查找自定义实现)。

附注。

myDic.AddOrUpdate(1, new Hashset<int>(){myFirstElement}, 

每次调用AddOrUpdate时,即使字典中已经存在该键,您也会创建一个新的HashSet。相反,请使用添加值工厂的重载版本:
myDic.AddOrUpdate(1, (key) => new HashSet<int>() { myFirstElement },

编辑:使用 ConcurrentDictionary 作为哈希集合的示例用法:

var myDic = new ConcurrentDictionary<long, ConcurrentDictionary<int, byte>>();
long key = 1;
int element = 1;
var hashSet = myDic.AddOrUpdate(key, 
    _ => new ConcurrentDictionary<int, byte>(new[] {new KeyValuePair<int, byte>(element, 0)}),
    (_, oldValue) => {
        oldValue.TryAdd(element, 0);
        return oldValue;
    });

非常感谢您的建议。您能否写一个示例,说明我如何使用带有虚拟键的ConcurrentDictionary作为值?谢谢。 - Álvaro García
在这个例子中,在我获取哈希集并尝试添加新元素之间,有人可能会删除它,因此当我尝试添加时,我可能会遇到错误,或者至少我会向哈希集中添加一个字典中不存在的元素。这正确吗? - Álvaro García
@ÁlvaroGarcía 是的,那是正确的(没有错误,但你将添加到哈希集合中不再存在的元素)。如果这对你来说是个问题 - 我认为你可以像最初一样使用AddOrUpdate(当然要用ConcurrentDictionary而不是HashSet)。 - Evk
我在想,可以用你的便笺方式并改用字典而不是哈希集来实现第一种方法。非常感谢。 - Álvaro García

1
如果您将匿名函数定义放在花括号中,您可以在函数体中定义多个语句,并以此指定返回值,如下所示:
myDic.AddOrUpdate(1, new HashSet<int>() { myFirstElement },
(key, actualValue) => {
    actualValue.Add(myElement);
    return actualValue;
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接