Tenter de synchroniser en interne sera presque certainement insuffisant car c'est à un niveau d'abstraction trop bas. Disons que vous rendez les opérations Add
et ContainsKey
individuellement thread-safe comme suit:
public void Add(TKey key, TValue value)
{
lock (this.syncRoot)
{
this.innerDictionary.Add(key, value);
}
}
public bool ContainsKey(TKey key)
{
lock (this.syncRoot)
{
return this.innerDictionary.ContainsKey(key);
}
}
Alors que se passe-t-il lorsque vous appelez ce bit de code censé être thread-safe à partir de plusieurs threads? Cela fonctionnera-t-il toujours bien?
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
La réponse simple est non. À un moment donné, la Add
méthode lèvera une exception indiquant que la clé existe déjà dans le dictionnaire. Comment cela peut-il être avec un dictionnaire thread-safe, demandez-vous? Eh bien, juste parce que chaque opération est thread-safe, la combinaison de deux opérations ne l'est pas, car un autre thread pourrait la modifier entre votre appel à ContainsKey
etAdd
.
Ce qui signifie pour écrire correctement ce type de scénario, vous avez besoin d'un verrou en dehors du dictionnaire, par exemple
lock (mySafeDictionary)
{
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
}
Mais maintenant, étant donné que vous devez écrire du code de verrouillage externe, vous mélangez la synchronisation interne et externe, ce qui conduit toujours à des problèmes tels que du code peu clair et des blocages. Donc, en fin de compte, vous êtes probablement mieux:
Utilisez une normale Dictionary<TKey, TValue>
et synchronisez en externe, en y englobant les opérations composées, ou
Ecrivez un nouveau wrapper thread-safe avec une interface différente (c'est-à-dire pas IDictionary<T>
) qui combine les opérations telles qu'une AddIfNotContained
méthode afin que vous n'ayez jamais besoin de combiner des opérations à partir de celle-ci.
(J'ai tendance à aller avec # 1 moi-même)