Из того, что я прочитал, нет гарантии, что метод фабрики Add будет вызываться только один раз среди всех вызывающих, чтобы получить один и тот же ключ .
Соответствующая часть этой страницы находится внизу, здесь указано:
Кроме того, хотя все методы ConcurrentDictionary (Of TKey, TValue)
потокобезопасны, не все методы являются атомарными, в частности GetOrAdd и
AddOrUpdate. Пользовательский делегат, который передается этим методам:
вызывается вне внутренней блокировки словаря. (Это сделано для
предотвратить неизвестный код от блокировки всех потоков.) Поэтому это
возможно для этой последовательности событий произойти:
1) threadA вызывает GetOrAdd, не находит элемента и создает новый элемент для добавления.
вызывая делегат valueFactory.
2) threadB одновременно вызывает GetOrAdd, его делегат valueFactory
вызывается, и он достигает внутренней блокировки перед threadA, и поэтому его
новая пара ключ-значение добавлена в словарь.
3) Пользовательский делегат threadA завершается, и поток достигает
заблокировать, но теперь видит, что элемент уже существует
4) threadA выполняет «Get» и возвращает данные, которые были ранее
добавлено автором B.
Следовательно, не гарантируется, что данные, возвращаемые
GetOrAdd - это те же данные, которые были созданы потоком
valueFactory. Подобная последовательность событий может произойти, когда AddOrUpdate
называется.
Способ, который я прочитал, заключается в том, что даже если вы вызываете какую-то блокировку в вашем делегате Add, вы не гарантируете, что значение, возвращаемое из вашего add, будет тем, которое будет фактически использоваться.
Итак, вам не нужно добавлять какие-либо дополнительные блокировки, и вместо этого вы можете использовать следующий шаблон:
private ConcurrentDictionary<Type, ISomeObject> someObjectCache =
new ConcurrentDictionary<Type, ISomeObject>();
public ISomeObject CreateSomeObject(Type someType)
{
ISomeObject someObject;
if (someObjectCache.TryGet(someType, out someObject))
{
return someObject;
}
if (Attribute.IsDefined(someType, typeof(SomeAttribute))
{
// init someObject here
someObject = new SomeObject();
return someObjectCache.GetOrAdd(someType, someObject); // If another thread got through here first, we'll return their object here.
}
// fallback functionality goes here if it doesn't have your attribute.
}
Да, это может привести к потенциальной возможности создания новых объектов несколько раз, но все вызывающие стороны получат один и тот же результат, даже если вызывается несколько. То же, что и GetOrAdd сейчас.