Tentar sincronizar internamente quase certamente será insuficiente porque está em um nível de abstração muito baixo. Digamos que você torne as operações Add
e ContainsKey
individualmente thread-safe da seguinte maneira:
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);
}
}
Então, o que acontece quando você chama esse pedaço de código supostamente thread-safe a partir de vários threads? Sempre funcionará bem?
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
A resposta simples é não. Em algum ponto, o Add
método lançará uma exceção indicando que a chave já existe no dicionário. Como pode ser isso com um dicionário thread-safe, você pode perguntar? Bem, só porque cada operação é segura para thread, a combinação de duas operações não é, já que outra thread poderia modificá-la entre sua chamada para ContainsKey
e Add
.
O que significa que para escrever este tipo de cenário corretamente, você precisa de um cadeado fora do dicionário, por exemplo
lock (mySafeDictionary)
{
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
}
Mas agora, visto que você está tendo que escrever código de bloqueio externo, você está misturando a sincronização interna e externa, o que sempre leva a problemas como código pouco claro e deadlocks. Então, no final das contas, você provavelmente é melhor:
Use um normal Dictionary<TKey, TValue>
e sincronize externamente, envolvendo as operações compostas nele, ou
Escreva um novo wrapper thread-safe com uma interface diferente (ou seja, não IDictionary<T>
) que combine as operações, como um AddIfNotContained
método, para que você nunca precise combinar as operações a partir dele.
(Eu mesmo costumo ir com o nº 1)