При выполнении параллельных запросов в PLINQ также могут возникать различные исключения, которые необходимо каким-либо образом обрабатывать. При выполнении параллельных запросов PLINQ разбивает исходную последовательность на части, каждая из которых обрабатывается в отдельном потоке. При этом, если в каких-либо потоках возникают исключения, то все они собираются в одном объекте исключений типа AggregateException.
Обработка исключений в PLINQ
Рассмотрим следующий пример:
static void Main(string[] args)
{
int[] ints = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
var query = ints.AsParallel().Select(i => 2 / i);
try
{
query.ForAll(Console.WriteLine);
}
catch (AggregateException ex)
{
Console.WriteLine(ex.Message);
}
}
Здесь мы создали запрос PLINQ в котором вычисляется значение » два поделить на элемент массива ints«. Так как в массиве присутствует значение 0, то это неизбежно должно привести к исключению типа System.DivideByZeroException как только очередной поток начнет вычислять выражение «2/0». Далее, в блоке try...catch мы выполняем запрос, используя метод ForAll PLINQ.
Теперь, если мы запустим приложение без отладки (Ctrl+F5), то увидим в консоли сообщение следующего содержания:
Если же приложение запустится в режиме отладки, то выполнение прервется на следующей строке:
Если мы продолжим выполнение приложение, то в консоль также выведется сообщение об ошибке.
Отмена параллельного запроса
Вполне возможно, что параллельный запрос может выполняться достаточно продолжительное время и нам потребуется механизм его отмены. Чтобы иметь возможность отмены параллельного запроса нам необходимо использовать метод WithCancellation(), передав ему в качестве параметра токен CancellationToken.
static void Main(string[] args)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Task task = Task.Run(() =>
{
Task.Delay(100);
cancellationTokenSource.Cancel(); //отменяем выполнение параллельного запроса
});
int[] ints = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
var query = ints.AsParallel().Select(i => 2 / i).WithCancellation(cancellationTokenSource.Token);
try
{
query.ForAll(Console.WriteLine);
}
catch (AggregateException ex)
{
Console.WriteLine(ex.Message);
}
catch (OperationCanceledException cancel)
{
Console.WriteLine("Запрос прерван");
}
finally
{
cancellationTokenSource.Dispose();
}
}
Для отмены операции мы запускаем отдельную задачу, которая через 100 мс. вызывает отмену (в приложении с графическим интерфейсом мы могли бы, например, «повесить» вызов сancellationTokenSource.Cancel() на клик по кнопке) . При отмене операции вызывается исключение типа OperationCanceledException , которое мы также перехватываем в блоке try..catch. Так как ещё до отмены задачи может быть вызвано исключение AggregateException, то его мы также обрабатываем.
Итого
Исключение, генерируемые в отдельных потоках при выполнении параллельного запроса PLINQ собираются в объекте AggregateException. Для обработки исключений PLINQ мы используем блоки try..catch. Чтобы отменить выполнение параллельного запроса мы должны использовать в запросе метод WithCancellation() в которые передается токен CancellationToken.