Как добавить новый элемент в хэш-набор, который является значением ConcurrentDictionary?

У меня есть ConcurrentDictionary, который имеет ключ long и значение хэш-набора int. Я хочу, чтобы, если ключа нет в словаре, добавить новый хэш-набор с первым элементом. Если ключ существует, добавьте новый элемент в существующий словарь.

Я пытаюсь что-то вроде этого:

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 ожидает хэш-набор. Первый и второй параметры правильные.

Итак, мой вопрос заключается в том, как я могу добавить новый элемент в хэш-набор потокобезопасным способом и избежать дублирования (это причина, по которой я использую хэш-набор в качестве значения). Проблема хэш-набора в том, что он не является потокобезопасным, и если я сначала получу его, а потом добавлю новый элемент, то я работаю вне словаря, и у меня могут возникнуть проблемы.

Спасибо.


person Álvaro García    schedule 31.10.2017    source источник


Ответы (2)


Чтобы исправить ошибку компилятора, вы можете сделать это:

myDic.AddOrUpdate(1, new HashSet<int>() { myFirstElement },
    (key, actualValue) => {
        actualValue.Add(myFirstElement);
        return actualValue;
    });

НО это не потокобезопасно, потому что функция «обновить» не запускается внутри какой-либо блокировки, поэтому вы потенциально добавляете к не потокобезопасному HashSet из нескольких потоков. Это может привести (например) к потере значений (например, вы добавляли 1000 элементов в HashSet, но в итоге у вас, например, было только 970 элементов). Функция обновления в AddOrUpdate не должна иметь побочных эффектов, а здесь она есть.

Вы можете заблокировать себя при добавлении значений в HashSet:

myDic.AddOrUpdate(1, new HashSet<int>() { myFirstElement },
    (key, actualValue) => {
        lock (actualValue) {
            actualValue.Add(myFirstElement);
            return actualValue;
        }
    });

Но тогда возникает вопрос, почему вы в первую очередь используете структуру без блокировки (ConcurrentDictionary). Кроме того, любой другой код может получить HashSet из вашего словаря и добавить туда значение без каких-либо блокировок, что сделает все это бесполезным. Поэтому, если вы решили пойти по этому пути по какой-либо причине, вы должны убедиться, что весь код блокируется при доступе к HashSet из этого словаря.

Вместо всего этого - просто используйте concurrent collection вместо HashSet. Насколько мне известно, ConcurrentHashSet нет, но вы можете использовать другой ConcurrentDictionary с фиктивными ключами в качестве замены (или поискать в Интернете пользовательские реализации).

Примечание. Здесь

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

вы создаете новый HashSet каждый раз при вызове AddOrUpdate, даже если этот словарь не нужен, потому что ключ уже есть. Вместо этого используйте перегрузку с фабрикой добавленной стоимости:

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;
    });
person Evk    schedule 31.10.2017
comment
Большое спасибо за ваши советы. Не могли бы вы привести пример, как я мог бы использовать CouncurrentDictionary в качестве значения с фиктивными ключами? Спасибо. - person Álvaro García; 31.10.2017
comment
В этом примере. Между тем, когда я получаю хеш-набор и пытаюсь добавить новый элемент, кто-то может его удалить, поэтому, когда я пытаюсь добавить, я могу получить ошибку или, по крайней мере, добавить элемент в хэш, которого нет в словаре. . Это правильно? - person Álvaro García; 31.10.2017
comment
@ ÁlvaroGarcía Да, это правильно (без ошибок, но вы добавите в хэш-набор больше нет). Если это проблема для вас - я думаю, вы можете использовать AddOrUpdate, как и изначально (конечно, с ConcurrentDictionary вместо HashSet). - person Evk; 31.10.2017
comment
Я думал об этом, сделайте первый способ со своим примечанием и измените словарь вместо хэш-набора. Большое спасибо. - person Álvaro García; 31.10.2017

Если вы заключите определение анонимной функции в фигурные скобки, вы можете определить несколько операторов в теле функции и, таким образом, указать возвращаемое значение следующим образом:

myDic.AddOrUpdate(1, new HashSet<int>() { myFirstElement },
(key, actualValue) => {
    actualValue.Add(myElement);
    return actualValue;
});
person Conyc    schedule 31.10.2017