Из документации метода ConcurrentDictionary.AddOrUpdate
:
Для модификаций и операций записи в словарь ConcurrentDictionary<TKey,TValue>
использует мелкозернистую блокировку для обеспечения безопасности потока. (Операции чтения в словаре выполняются без блокировки.) Однако делегаты addValueFactory
и updateValueFactory
вызываются вне блокировок , чтобы избежать проблем, которые могут возникнуть из-за выполнения неизвестного кода взамок. Следовательно, AddOrUpdate
не является атомарным в отношении всех других операций в классе ConcurrentDictionary<TKey,TValue>
.
(выделение добавлено)
Так что вы не можете использовать HashSet
в качествезначение ConcurrentDictionary
и обновлять его из нескольких потоков без защиты. Он станет испорченным и начнет генерировать случайные исключения, подобные тем, которые вы наблюдаете. Вы должны либо защитить его с помощью блокировки (используя разные объекты блокировки для каждого HashSet
, чтобы уменьшить конкуренцию), либо использовать одновременный HashSet
(класс ConcurrentHashSet
отсутствует, поэтому вы должны использоватьnested ConcurrentDictionary
).
Что касается первого варианта, включающего lock
, вы должны использовать один и тот же объект блокировки везде, где у вас есть доступ к одному и тому же HashSet
, а не только внутри функции обратного вызова * 1031. * method.
Возможно, однако, что вся эта синхронизация, которая добавляет накладные расходы вашему приложению, может быть удалена с использованием подхода рабочего процесса (подход, одобренный библиотекой TPL Dataflow ). Или это может быть невозможно. Это зависит от специфики того, что вы делаете.