Является ли HashSet <T>потокобезопасным в качестве значения ConcurrentDictionary <TKey, HashSet <T>>? - PullRequest
0 голосов
/ 20 ноября 2018

Если у меня есть следующий код:

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 по своей сути потокобезопасным, поскольку HashSet является свойством значения параллельного словаря?

Ответы [ 3 ]

0 голосов
/ 20 ноября 2018

Нет. Помещение контейнера в потокобезопасный контейнер не делает внутреннюю резьбу контейнера безопасной.

dictionary[user.GroupId].Add(user.Id.ToString());

вызывает добавление HashSet после извлечения его из ConcurrentDictionary. Если этот 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())
}
0 голосов
/ 20 ноября 2018

Нет, коллекция (сам словарь) является поточно-ориентированной, а не той, которую вы в нее помещаете. У вас есть несколько вариантов:

  1. Используйте AddOrUpdate как упомянутое @TheGeneral:

    dictionary.AddOrUpdate(user.GroupId,  new HashSet<string>(), (k,v) => v.Add(user.Id.ToString());
    
  2. Используйте одновременную коллекцию, например 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());
        }
    }
}
  1. Если вы на самом деле хотите встроенную параллельную HashSet-подобную коллекцию, вам нужно будет использовать ConcurrentDictionary<int, ConcurrentDictionary<string, string>> и позаботиться о ключе или значении из внутреннего.
0 голосов
/ 20 ноября 2018

Использование 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());

Или, как Камило Теревинто сказал, ConcurrentBag, вероятно, там, где вы хотите быть

...