トップレベル関数が次のように機能するアプリケーションがよくあります
public Result5 ProcessAll() {
var result1 = Process1();
var result2 = Process2();
var result3 = Process3(result1);
var result4 = Process4(result1, result2);
return Process5(result1, result2, result3, result4);
}
Process* 関数に共通するものは次のとおりです。
- IO バウンド (データベース、ファイルシステム、Web サービス)
- コールスタックに伝播されたばかりの例外をスローする可能性があります
- 処理を停止して返すだけの例外的ではないエラーに対してエラーを返す場合があります
最上位の関数は、キャンセル可能なバックグラウンド スレッドでも実行されています。これは、完全な実装が次のようになることを意味します
public Result5 ProcessAll(CancellationToken cancellationToken) {
Result1 result1 = Process1();
if (result1 == null)
return null;
cancellationToken.ThrowIfCancellationRequested();
Result2 result2 = Process2();
if (result2 == null)
return null;
cancellationToken.ThrowIfCancellationRequested();
Result3 result3 = Process3(result1);
if (result3 == null)
return null;
cancellationToken.ThrowIfCancellationRequested();
Result4 result4 = Process4(result1, result2);
if (result4 == null)
return null;
cancellationToken.ThrowIfCancellationRequested();
return Process5(result1, result2, result3, result4);
}
ここで、可能な限り並行して実行することで処理を高速化する必要があると仮定しましょう。
また、Process* 関数がタスク非同期パターンを実装し、IO Completion ポートなどを使用するとします。
私はこれに適したパターンを見つけることができませんでした。
エラー/例外/キャンセルを無視すると、次のようになります。
public Result5 ProcessAll(CancellationToken cancellationToken) {
Task<Result1> task1 = Process1Async();
Task<Result2> task2 = Process2Async();
Task<Result3> task3 = task1.ContinueWith(_ => Process3Async(task1.Result)).Unwrap();
Task<Result4> task4 = Task.Factory.ContinueWhenAll(new[] { task1, task2 },
_ => Process4Async(task1.Result, task2.Result)).Unwrap();
// This will trigger all exceptions captured
Task.WaitAll(new[] { task1, task2, task3, task4 });
return Process5(task1.Result, task2.Result, task3.Result, task4.Result);
}
(これは task4 同期を実行するように最適化でき、WaitAll は必要ないことはわかっていますが、ここではパターンを示しているだけです)
エラーと例外を処理しようとすると、次のようになります。
public Result ProcessAll(CancellationToken cancellationToken) {
Task<Result1> task1 = Process1Async();
Task<Result2> task2 = Process2Async();
// Process 3 should not run if task1 or task2 failed or returned error
Task<Result3> task3 = task1.ContinueWith(_ => {
if (task1.IsFaulted || task1.Result == null)
return null;
if (task2.IsFaulted || (task2.IsCompleted && task2.Result == null)
return null;
return Process3Async(task1.Result);
}).Unwrap();
// Process4 should not start if any of Process1,Process2 or Process3 returned error or throw exception
Task<Result4> task4 = Task.Factory.ContinueWhenAll(new[] { task1, task2 }, _ => {
if (task1.Faulted || task1.Result == null)
return null;
if (task2.Faulted || task2.Result == null)
return null;
if (task3.Faulted || (task3.IsCompleted && task3.Result == null))
return null;
return Process4Async(task1.Result, task2.Result)).Unwrap();
Task.WaitAll(new[] { task1, task2, task3, task4 });
if (task1.Result == null ||
task2.Result == null ||
task3.Result == null ||
task4.Result == null)
return null;
return Process5(task1.Result, task2.Result, task3.Result, task4.Result);
}
そして今、キャンセルチェックを入れる必要があります:-)
私の質問は次のとおりです。
以前のタスクでの失敗、エラー、およびキャンセルのこれらすべてのチェックは、エラーが発生しやすくなり、あまりスケーラブルではありません。ここで重要なことを見逃していて、間違った方法で行っていますか?