[C#] Handling Multiple Errors in PLINQ Parallel Processing (AggregateException)

When an exception occurs during parallel processing using PLINQ (Parallel LINQ), the behavior differs from standard execution. Since errors can happen simultaneously on multiple threads, the .NET Framework collects all these exceptions into a single container called AggregateException and throws it.

This article explains how to properly catch and handle all errors that occur during parallel processing, assuming a scenario where data defects cause exceptions.

目次

Implementing Exception Handling with AggregateException

While a standard try-catch (Exception ex) block can only handle the first caught exception, catching AggregateException in PLINQ allows you to access the list of all exceptions (InnerExceptions) that occurred during execution.

The following code demonstrates parallel calculation on a list containing incomplete data (null) and invalid values. It catches errors and logs them according to the error type.

Sample Code

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        // Test Data: Includes nulls and uncalculable values (0)
        var workItems = new WorkItem[]
        {
            new WorkItem { Id = "A001", Value = 100 },
            null,                                      // Causes NullReferenceException
            new WorkItem { Id = "A003", Value = 50 },
            new WorkItem { Id = "A004", Value = 0 },   // Causes DivideByZeroException
            new WorkItem { Id = "A005", Value = 200 },
        };

        Console.WriteLine("Starting parallel calculation...");

        // Define PLINQ Query (Not executed yet)
        var query = workItems
            .AsParallel()
            .WithDegreeOfParallelism(2) // Ensure execution across multiple threads
            .Select(item =>
            {
                // Exceptions may occur here
                // if item is null -> NullReferenceException
                // if Value is 0 -> DivideByZeroException
                return new
                {
                    item.Id,
                    CalculatedResult = 1000 / item.Value
                };
            });

        try
        {
            // The query executes the moment it is enumerated (foreach), and exceptions occur
            foreach (var result in query)
            {
                Console.WriteLine($"[Success] ID:{result.Id} Result:{result.CalculatedResult}");
            }
        }
        catch (AggregateException ae)
        {
            // All exceptions from PLINQ are wrapped in AggregateException
            Console.WriteLine("\n--- Errors Occurred ---");

            // Individual exceptions are stored in the InnerExceptions property
            foreach (var ex in ae.InnerExceptions)
            {
                if (ex is NullReferenceException)
                {
                    Console.WriteLine("Error: Found a null element.");
                }
                else if (ex is DivideByZeroException)
                {
                    Console.WriteLine("Error: Division by zero occurred.");
                }
                else
                {
                    Console.WriteLine($"Other Error: {ex.Message}");
                }
            }
        }
        
        Console.WriteLine("\nProcessing finished.");
    }
}

// Data Class Definition
class WorkItem
{
    public string Id { get; set; }
    public int Value { get; set; }
}

Explanation and Technical Points

1. Exception Aggregation

In parallel processing, if an error occurs on Thread A, another error might occur simultaneously on Thread B. Instead of stopping immediately, PLINQ collects these exceptions as much as possible and notifies the caller by wrapping them in an AggregateException when processing completes (or aborts).

2. Utilizing InnerExceptions

The Message property of AggregateException itself usually contains only a generic message like “One or more errors occurred.” To understand the specific error details, you must loop through the InnerExceptions collection to check its contents.

3. Flatten() Method

If exceptions are nested (e.g., an AggregateException inside another AggregateException), using ae.Flatten().InnerExceptions allows you to retrieve all exceptions as a flat, one-dimensional list, making handling easier.

Note

Exception handling is a costly operation. Whenever possible, instead of relying on try-catch, it is better for performance to filter out invalid data within the query using checks like if (item != null && item.Value != 0).

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次