Попытки внутренней синхронизации почти наверняка будет недостаточно, потому что она находится на слишком низком уровне абстракции. Скажем , вы делаете Add
и ContainsKey
операции по отдельности поточно- следующим образом :
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);
}
}
Что тогда происходит, когда вы вызываете этот якобы потокобезопасный фрагмент кода из нескольких потоков? Всегда ли будет работать нормально?
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
Простой ответ - нет. В какой-то момент Add
метод вызовет исключение, указывающее, что ключ уже существует в словаре. Вы можете спросить, как это может быть с поточно-ориентированным словарем? Просто потому, что каждая операция является потокобезопасной, комбинация двух операций - нет, поскольку другой поток может изменить ее между вашим вызовом ContainsKey
и Add
.
Это означает, что для правильного написания этого типа сценария вам понадобится блокировка вне словаря, например
lock (mySafeDictionary)
{
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
}
Но теперь, когда вам нужно написать код внешней блокировки, вы смешиваете внутреннюю и внешнюю синхронизацию, что всегда приводит к таким проблемам, как нечеткий код и взаимоблокировки. Так что в конечном итоге вам, вероятно, лучше:
Используйте обычный Dictionary<TKey, TValue>
и внешнюю синхронизацию, включив в него составные операции, или
Напишите новую потокобезопасную оболочку с другим интерфейсом (т. Е. Нет IDictionary<T>
), которая объединяет такие операции, как AddIfNotContained
метод, поэтому вам никогда не придется комбинировать операции из него.
(Я сам предпочитаю №1)