新しもの好きプログラマの耳より情報ブログ

仕事でもあるプログラミングについて役に立ちそうな情報を発信していこうというブログです。役に立たなそうな情報はfacebookで。

C#のTaskを同期メソッドの中でawait風に待つやり方

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