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()を使う。