Метод AddOrUpdate ConcurrentDictionary, выбрасывающий исключение IndexOutOfRangeException - PullRequest
0 голосов
/ 09 ноября 2019

Задания добавляются в HashSet различными потоками и выдают эту ошибку. Есть ли какое-то решение для этого?

ConcurrentDictionary<myKey, HashSet<Job>> _dictKeyJob;

_dictKeyJob.AddOrUpdate(myKey, key =>
{
    return new HashSet<Job>({ Job };
}, (key, hashJobs) =>
{
    if (Job.Status == eStatus.Cancelled)
    {
        hashJobs.Remove(Job);
    }
    else
    {
        hashJobs.Add(Job);
    }
    return hashJobs;
});

Исключение:

System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at System.Collections.Generic.HashSet`1.SetCapacity(Int32 newSize, Boolean forceNewHashCodes)
   at System.Collections.Generic.HashSet`1.AddIfNotPresent(T value)
   at Raj.OPS.Common.Test.<>c__DisplayClass38_0.<SetOrAddKey>b__1(mKey key, HashSet`1 hashJobs) in 
   at System.Collections.Concurrent.ConcurrentDictionary`2.**AddOrUpdate**(TKey key, Func`2 addValueFactory, Func`3 updateValueFactory)

1 Ответ

0 голосов
/ 15 ноября 2019

Из документации метода ConcurrentDictionary.AddOrUpdate:

Для модификаций и операций записи в словарь ConcurrentDictionary<TKey,TValue> использует мелкозернистую блокировку для обеспечения безопасности потока. (Операции чтения в словаре выполняются без блокировки.) Однако делегаты addValueFactory и updateValueFactory вызываются вне блокировок , чтобы избежать проблем, которые могут возникнуть из-за выполнения неизвестного кода взамок. Следовательно, AddOrUpdate не является атомарным в отношении всех других операций в классе ConcurrentDictionary<TKey,TValue>.

(выделение добавлено)

Так что вы не можете использовать HashSet в качествезначение ConcurrentDictionary и обновлять его из нескольких потоков без защиты. Он станет испорченным и начнет генерировать случайные исключения, подобные тем, которые вы наблюдаете. Вы должны либо защитить его с помощью блокировки (используя разные объекты блокировки для каждого HashSet, чтобы уменьшить конкуренцию), либо использовать одновременный HashSet (класс ConcurrentHashSet отсутствует, поэтому вы должны использоватьnested ConcurrentDictionary).

Что касается первого варианта, включающего lock, вы должны использовать один и тот же объект блокировки везде, где у вас есть доступ к одному и тому же HashSet, а не только внутри функции обратного вызова * 1031. * method.

Возможно, однако, что вся эта синхронизация, которая добавляет накладные расходы вашему приложению, может быть удалена с использованием подхода рабочего процесса (подход, одобренный библиотекой TPL Dataflow ). Или это может быть невозможно. Это зависит от специфики того, что вы делаете.

...