C#の同期メソッドの中で、Taskの完了を待ちたい。しかし、単にWaitすると例外がAggregateExceptionとなって処理が面倒なので、awaitと同様にそのまま例外を受け取れる待ち方にしたい。
そんな都合のいい方法が・・・あった。ただし、状況によっては要求を満たさないので、使い分けになる。
1つだけのTaskの完了待ち
Taskに対して、GetAwaiter().GetResult()を呼ぶ。
void 同期メソッド()
{
var task1 = Task.Run(()=> return "result");
var resultStr = task1.GetAwaiter().GetResult();
}
といった要領になる。
要するに、「awaitと同様にそのまま例外を受け取れる待ち方」というのは、このGetAwaiter()が返すTaskAwaiterが実現しているという事のようだ。単に1つだけのTaskを無限に待って、例外をシンプルに処理したい場合は、この呼び出し方が良いだろう。
ただし、GetAwaiter()の場合は、その完了待ち処理自体をCancelletionTokenでキャンセルすることはできない(引数にCancellationTokenを渡せない)。そのため、待つ対象のTaskがキャンセル可能でない場合は、無限待ちになる。そうしたケースで、完了待ち処理自体をキャンセルしたい場合、引き続きWaitを使う必要がある。
void 同期メソッド()
{
try {
var task1 = Task.Run(()=>
{
//キャンセル出来ない長時間の処理
return "result";
});
var resultStr = task1.Wait(cancelToken);
}
catch(OperationCanceledException){
//待ち自体がキャンセルとなった場合の処理
}
catch(AggregateException){
//タスク内で例外が発生した場合の処理
}
}
このような形になるだろう。
複数のタスクの完了待ち
複数のタスクをまとめて待ちたい場合も、WaitAll()の代わりにWhenAll().GetAwaiter().GetResult()で簡易に処理ができる。この場合、発生した例外の中で1つだけがそのまま返ってくる。
ただしGetAwaiter()を利用した場合は、例外は1つだけしか取れない。複数のタスクの例外を全て集約したい意図があって、むしろAggregateExceptionが欲しい場合は、引き続きWaitAll()を使う。