xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

非同期メソッド入門 (2) - async修飾子とawait演算子

非同期メソッドを実装する上で必要なキーワードはasync修飾子とawait演算子のふたつです。まずはこれらを押さえておきましょう。

※今回の内容はTPL入門(20) - 非同期メソッドの繰り返しです。ごめんなさい...

async修飾子

async修飾子はメソッド内でawait演算子を利用するためのキーワードです。メソッドをasyncで修飾した場合はメソッド内に1つ以上のawait演算子を含まなければならない、という規約です。awaitしたい場合はasyncでメソッドを修飾しなければならない、とも言えます。そしてこのasyncで修飾されたメソッドを「非同期メソッド」と呼びます。以下はその簡単な例です。

private async void Button_Click(object sender, RoutedEventArgs e)
{
    this.button.IsEnabled = false;
    await HeavyWork();    //--- 何か重たい処理
    this.button.IsEnabled = true;
}

async修飾子はただの目印でしかなく、コンパイル結果は通常のメソッドと変わりません。asyncを付加する理由はC# 4.0以前のコードとの互換のため、と言われています。なのでasync修飾子を付加したからと言って、そのメソッドが別スレッド上で非同期的に動作するという意味にはなりません。async修飾子の意味は、「このメソッドは非同期操作を待つ必要がある制御フロー (await) を含んでおり、非同期処理の適切な時点でこのメソッドを再度途中から始められるようにコンパイラによって継続渡しに書き直される」です。実際とは異なりますが、意味としては以下のようなコードになります。

private void Button_Click(object sender, RoutedEventArgs e)
{
    this.button.IsEnabled = false;
    HeavyWork().ContinueWith(_ =>    //--- UIに同期的な継続処理
    {
        this.button.IsEnabled = true;
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

また、非同期メソッドの戻り値は、void、Task、Task<T>のいずれかである必要があります。

await演算子

await演算子はasync修飾子の付くメソッドの中で1つ以上記述することができます。await演算子にはGetAwaiterメソッド (もしくは同名の拡張メソッド) を実装した型を渡すことができます。現在.NET Frameworkによって提供されている型では、Taskクラスが該当します。

await演算子は、awaitに渡される処理が完了するまでそれ以降の実行を中断し、制御をすぐに呼び出し元に戻します。awaitに渡された処理が完了したら、メソッド内に残されたawait以降の処理を「継続」として実行します

private async void Button_Click(object sender, RoutedEventArgs e)
{
    this.button.IsEnabled = false;
    await HeavyWork();             //--- この重たい処理を開始したら即座に関数からreturn
    this.button.IsEnabled = true;  //--- 重たい処理完了後に「継続」として呼び出される
}

しかしながら、必ずしもawait以降の処理が非同期処理の完了後の継続として実行されるとは限りません。await以降を継続として登録する前に非同期処理が完了してしまった場合は、以降の処理は継続ではなく、通常のメソッドと同様に同期的に実行されます。

注意が必要なのは、await演算子は「非同期処理が終了するまで呼び出し元スレッドをブロックして待機するという意味ではない」ということです。