Цикл Parallel.Foreach, несовместимое поведение с явным оператором throw
Создал простую программу с использованием Linqpad, где я бросаю исключение явно в Parallel Foreach
цикл, который в идеале должен быть пойман в вызывающем как Aggregate Exception
, но когда я явно выбрасываю исключение, оно иногда пропускает несколько исключений случайным образом. Я не в состоянии понять поведение, любой, кто может объяснить
void Main()
{
try
{
var intList = new List<int> {1,2,3,4,5,6};
Parallel.ForEach(intList, i => Test1(i));
}
catch (AggregateException aggregateException)
{
foreach (var ex in aggregateException.Flatten().InnerExceptions)
{
ex.Message.Dump();
}
}
}
public void Test1(int i)
{
try
{
if (i % 2 != 0)
throw new Exception($"{i} - Odd value exception");
}
catch(Exception ex)
{
ex.Message.Dump();
throw;
}
}
public void Test2(int i)
{
if (i % 2 != 0)
throw new Exception($"{i} - Odd value exception");
}
public void Test3(int i)
{
try
{
if (i % 2 != 0)
throw new Exception($"{i} - Odd value exception");
}
catch(Exception ex)
{
ex.Message.Dump();
}
}
Подробности:
- Там две версии Test, одна с явным Try Catch, а другая без
- Оба имеют похожее противоречивое поведение в той степени, что в Test1 даже локальный try catch не выводит значение
- Там может быть третья версия
Test3
который всегда работает как исключение, явно не выбрасывается из параллельного цикла Dump
это вызов печати Linqpad заменить егоConsole.WriteLine
на визуальной студии
Здесь есть опция define, которая собирает все исключения в ConcurrentQueue
и бросить их позже как агрегированное исключение, но почему текущий код не работает должным образом, я не очень уверен. В этом случае мы ожидаем, что результат будет:
1 - Odd value exception
3 - Odd value exception
5 - Odd value exception
но некоторые из них пропускаются случайным образом, что также в простой программе, гораздо больше промахов в сложной программе, которая делает гораздо больше работы
1 ответ
Это вполне ожидаемое поведение.
Смотрите документы,
необработанное исключение приводит к немедленному завершению цикла
Когда вы выбрасываете исключение, новые задачи не будут запланированы.
Так что поведение будет казаться непредсказуемым. Вы не имеете права ожидать выполнения всех подзадач. Это не контракт цикла Parallel.For.
Разница будет намного понятнее, когда вы добавите больше элементов в исходный список. Выходные данные всегда будут отображать ряд исключений в окрестности ThreadPool.MinThreads.