Попытки внутренней синхронизации почти наверняка будут недостаточными, поскольку уровень абстракции слишком низок. Скажем, вы делаете операции 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)