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) のようなチェックを行い、不正なデータを除外(フィルタリング)する設計がパフォーマンス上は好ましいです。
