Цитировать документацию (выделено мной):
Для модификаций и операций записи в словарь, ConcurrentDictionary<TKey,TValue>
использует мелкозернистую блокировкудля обеспечения безопасности резьбы.(Операции чтения словаря выполняются без блокировок.) Однако делегат valueFactory
вызывается вне блокировок, чтобы избежать проблем, которые могут возникнуть при выполнении неизвестного кода под блокировкой.Следовательно, GetOrAdd
не является атомарным в отношении всех других операций класса ConcurrentDictionary<TKey,TValue>
.
Поскольку ключ / значение может быть вставлен другим потоком, пока генерируется valueFactory
значение, которому нельзя доверять только потому, что valueFactory
выполнено, его полученное значение будет вставлено в словарь и возвращено. Если вы вызываете GetOrAdd
одновременно в разных потоках, valueFactory
можно вызывать несколько раз, но в словарь будет добавлена только одна пара ключ / значение.
Такв то время как словарь является поточно-ориентированным, вызовы valueFactory
или _ => SampleTask()
в вашем случае не гарантируют, что они являются уникальными.Таким образом, ваша заводская функция должна соответствовать этому факту.
Вы можете подтвердить это из источника :
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
{
if (key == null) throw new ArgumentNullException("key");
if (valueFactory == null) throw new ArgumentNullException("valueFactory");
TValue resultingValue;
if (TryGetValue(key, out resultingValue))
{
return resultingValue;
}
TryAddInternal(key, valueFactory(key), false, true, out resultingValue);
return resultingValue;
}
Как видите, valueFactory
вызывается за пределами TryAddInternal
, который отвечает за правильную блокировку словаря.
Однако, поскольку valueFactory
является лямбда-функцией, которая возвращает задачу в вашем случае (_ => SampleTask()
), и словарь будетне дожидаясь выполнения этой задачи, функция завершит быстро и просто вернет неполное Task
после обнаружения первого await
(когда настроен асинхронный конечный автомат).Поэтому, если вызовы выполняются очень быстро за другим, задача должна быть очень быстро добавлена в словарь, и последующие вызовы будут повторно использовать ту же задачу.
Если требуется, чтобы это происходило один раз в all случаях, вы должны рассмотреть возможность блокировки при создании задачи самостоятельно.Поскольку он быстро завершится (независимо от того, сколько времени на самом деле потребуется для решения задачи), блокировка не повредит так сильно.