PLINQ + LINQ = НРЕ?

В чем причина случайных NRE при запуске кода ниже? Учитывая, что results инициализировано, как можно получить t в лямбде как null?

var results = new List<Result>();

for (int i = 0; i < 100; i++)
{
    Parallel.For((index) =>
        {
            results.Add(Result.Create(...));    
        });

    results = results.Where(t => t.IsValid).ToList(); // NRE here due to t is null!

}

person Dmitry    schedule 07.04.2015    source источник
comment
Ваш пример не компилируется и неверен. есть Parallel.For без коллекции.   -  person xanatos    schedule 07.04.2015


Ответы (1)


List<> не является потокобезопасным. Вы добавляете элементы из нескольких потоков. Если вы действительно хотите его использовать:

lock (results)
{
    results.Add(Result.Create(...));
}

Обратите внимание, что ваш пример неверен... Что-то, что компилируется и запускается:

var results = new List<Result>();

Parallel.For(0, 100, index =>
{
    lock (results)
    {
        results.Add(Result.Create(...));
    }
});

results = results.Where(t => t.IsValid).ToList(); // NRE here due to t is null!

или лучше

var results = new List<Result>();

Parallel.For(0, 100, index =>
{
    var result = Result.Create(...);

    lock (results)
    {
        results.Add(result);
    }
});

results = results.Where(t => t.IsValid).ToList(); // NRE here due to t is null!

чтобы создание result не блокировало запись List<> :-) В противном случае код бесполезен и выполнялся бы последовательно.

person xanatos    schedule 07.04.2015
comment
Или просто var results = Enumerable.Range(0, 100).AsParallel().Select(i => Result.Create(...)).Where(t => t.IsValid).ToList() - person sloth; 07.04.2015
comment
@sloth Да, если вы пытаетесь провести один из тех конкурсов, где вам нужно экономить на байтах или вы оцениваетесь по обфускации :-) Но я действительно думаю, что здесь есть по крайней мере разница ... Parallel.For знает диапазон номера, поэтому он имеет информацию Length, а Enumerable.Range(0, 100).AsParallel() не знает ее. Я не знаю, действительно ли эта информация используется. - person xanatos; 07.04.2015
comment
Правда, вы можете использовать var results = Enumerable.Range(0, 100).ToList().AsParallel()... для оптимизации, если это необходимо, чтобы разделитель по умолчанию мог лучше распределять рабочую нагрузку. - person sloth; 07.04.2015
comment
Блокировка здесь по существу преобразует код в последовательный. Лучше использовать ConcurrentQueue‹›, который не требует блокировки. - person Panagiotis Kanavos; 07.04.2015
comment
@PanagiotisKanavos Нет, если Result.Create очень медленный. Лучший вариант блокирует только results.Add. А для быстрого Result.Create Parallel.For хуже, чем бесполезен - person xanatos; 07.04.2015
comment
Вы также можете использовать ConcurrentBag‹T› так как он оптимизирован для этой ситуации. - person Caramiriel; 07.04.2015
comment
@Caramiriel Это правильно ... Если вы не хотите выполнять много операций только для чтения после окончания Parallel.For или если вам нужно / хотите использовать индексатор. При записи ConcurrentBag<> наверняка во много раз быстрее, чем lock + List.Add - person xanatos; 07.04.2015