【C#】PLINQ並列処理で発生する複数のエラーを一括捕捉する方法 (AggregateException)

PLINQ (Parallel LINQ) を使用した並列処理において例外が発生した場合、通常とは異なる挙動を示します。複数のスレッドで同時にエラーが発生する可能性があるため、.NETフレームワークはこれら全ての例外を AggregateException という一つのコンテナにまとめてスローします。

ここでは、データ不備による例外発生を想定し、並列処理中に起きた全てのエラーを適切にキャッチして処理する方法を解説します。

目次

AggregateExceptionによる例外処理の実装

通常の try-catch (Exception ex) では、最初に捕捉された例外しか扱えませんが、PLINQでは AggregateException を捕捉することで、実行中に発生したすべての例外リスト(InnerExceptions)にアクセスできます。

以下のコードは、不完全なデータ(null)や不正な値が含まれるリストを並列計算し、発生したエラーの種類ごとにログ出力を行うサンプルです。

サンプルコード

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

public class Program
{
    public static void Main()
    {
        // テストデータ: 一部に null や 計算不能な値(0)を含める
        var workItems = new WorkItem[]
        {
            new WorkItem { Id = "A001", Value = 100 },
            null,                                      // NullReferenceExceptionの原因
            new WorkItem { Id = "A003", Value = 50 },
            new WorkItem { Id = "A004", Value = 0 },   // DivideByZeroExceptionの原因
            new WorkItem { Id = "A005", Value = 200 },
        };

        Console.WriteLine("並列計算を開始します...");

        // PLINQクエリの定義(この時点では実行されません)
        var query = workItems
            .AsParallel()
            .WithDegreeOfParallelism(2) // 並列数を指定して複数スレッドでの実行を確実にする
            .Select(item =>
            {
                // ここで例外が発生する可能性があります
                // itemがnullなら NullReferenceException
                // Valueが0なら DivideByZeroException
                return new
                {
                    item.Id,
                    CalculatedResult = 1000 / item.Value
                };
            });

        try
        {
            // 列挙(foreach)した瞬間にクエリが実行され、例外が発生します
            foreach (var result in query)
            {
                Console.WriteLine($"[成功] ID:{result.Id} 結果:{result.CalculatedResult}");
            }
        }
        catch (AggregateException ae)
        {
            // PLINQで発生した例外はすべて AggregateException に梱包されています
            Console.WriteLine("\n--- エラーが発生しました ---");

            // InnerExceptionsプロパティに個々の例外が格納されています
            foreach (var ex in ae.InnerExceptions)
            {
                if (ex is NullReferenceException)
                {
                    Console.WriteLine("エラー: データがnullの要素が見つかりました。");
                }
                else if (ex is DivideByZeroException)
                {
                    Console.WriteLine("エラー: 0による除算が発生しました。");
                }
                else
                {
                    Console.WriteLine($"その他のエラー: {ex.Message}");
                }
            }
        }
        
        Console.WriteLine("\n処理を終了します。");
    }
}

// データクラス定義
class WorkItem
{
    public string Id { get; set; }
    public int Value { get; set; }
}

解説と技術的なポイント

1. 例外の集約 (Aggregation)

並列処理では、スレッドAでエラーが起きても、スレッドBでは別のエラーが同時に起きている可能性があります。PLINQはこれらを即座にスローして処理を中断させるのではなく、可能な限り収集し、処理完了(または中断)のタイミングで AggregateException としてまとめて呼び出し元に通知します。

2. InnerExceptions の活用

AggregateException 自体の Message プロパティには通常「1つ以上のエラーが発生しました」といった汎用的なメッセージしか入っていません。個別の具体的なエラー内容を知るには、必ず InnerExceptions コレクションをループ処理して中身を確認する必要があります。

3. Flatten() メソッド

例外がさらにネストしている場合(AggregateException の中に AggregateException がある場合など)、ae.Flatten().InnerExceptions を使用すると、すべての例外をフラットな1次元のリストとして取得でき、ハンドリングが容易になります。

注意点

例外処理はコストの高い操作です。可能な限り、try-catch に頼るのではなく、クエリ内で事前に if (item != null && item.Value != 0) のようなチェックを行い、不正なデータを除外(フィルタリング)する設計がパフォーマンス上は好ましいです。

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

この記事を書いた人

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

目次