Si j'ai le code suivant:

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());
}

L'action d'ajouter un élément dans le HashSet est-elle intrinsèquement thread-safe car HashSet est une propriété de valeur du dictionnaire concurrent?

4
Marko 20 nov. 2018 à 03:16

3 réponses

Meilleure réponse

Non, la collection (le dictionnaire lui-même) est thread-safe, pas ce que vous y mettez. Vous avez plusieurs options:

  1. Utilisez AddOrUpdate comme @TheGeneral l'a mentionné:

    dictionary.AddOrUpdate(user.GroupId,  new HashSet<string>(), (k,v) => v.Add(user.Id.ToString());
    
  2. Utilisez une collection simultanée, comme ConcurrentBag<T>:

    ConcurrentDictionary<int, ConcurrentBag<string>>
    

Chaque fois que vous construisez le dictionnaire, comme dans votre code, il vaut mieux y accéder le moins possible. Pensez à quelque chose comme ceci:

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. Si vous voulez réellement une collection de type HashSet simultanée intégrée, vous devez utiliser ConcurrentDictionary<int, ConcurrentDictionary<string, string>> et vous soucier de la clé ou de la valeur de la clé interne.
4
Camilo Terevinto 20 nov. 2018 à 00:41

Utiliser un ConcurrentDictionary comme celui-ci n'est pas sûr pour les threads

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

Utilisez plutôt

AddOrUpdate (TKey, TValue, Func)

dictionary.AddOrUpdate(user.GroupId,  new HashSet<string>(), (k,v) => v.Add(user.Id.ToString());

Ou comme Camilo Terevinto l'a dit ConcurrentBag est probablement là où vous voulez être

1
Michael Randall 20 nov. 2018 à 00:23

Non. Le fait de placer un conteneur dans un conteneur thread-safe ne garantit pas le thread du conteneur interne.

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

Appelle l'ajout de HashSet après l'avoir récupéré à partir du ConcurrentDictionary. Si ce GroupId est recherché à partir de deux threads à la fois, cela briserait votre code avec des modes d'échec étranges. J'ai vu le résultat de l'un de mes coéquipiers faisant l'erreur de ne pas verrouiller ses sets, et ce n'était pas joli.

C'est une solution plausible. Je ferais quelque chose de différent moi-même, mais c'est plus proche de votre code.

if (!dictionary.ContainsKey(user.GroupId)
{
    dictionary.TryAdd(user.GroupId, new HashSet<string>());
}
var groups = dictionary[user.GroupId];
lock(groups)
{
    groups.Add(user.Id.ToString())
}
5
Joshua 20 nov. 2018 à 00:34