連載:C# 5.0&VB 11.0新機能「async/await非同期メソッド」入門

第2回 非同期メソッドの構文

鈴木 孝明
2012/09/25
Page1 Page2 Page3

 前回は、.NET Frameworkが提供してきた非同期処理システムの歴史に沿いながら、最新の非同期メソッドまでの進化について見てきた。その進歩は著しく、.NET Frameworkの黎明期(れいめいき)と比べると「まるで魔法」と言ってもよいくらいだ。初めてご覧になった方や以前の書き方で非同期処理に良い思い出がなかった方は、きっと衝撃を受けたことだろう。

 今回は、そんな魔法のような非同期メソッドの構文や使い方、注意点などについて解説する。これを読めば非同期メソッドを平時使ううえで困ることはほぼほぼなくなるだろう。さぁ、始めよう。

非同期メソッドのキーワード

 非同期メソッドを利用するうえで必要なキーワードは、async修飾子とawait演算子の2つだ。まずはこれらのキーワードの意味を押さえておこう。

async修飾子

 async修飾子は、修飾したメソッド内でawait演算子(後述)を利用するためのキーワードだ。「“async”で修飾されたメソッド内にはawait演算子を1つ以上含む」という規約となっている。逆に言えば、「await演算子を利用する場合は、そのメソッドにasync修飾子を付けなければならない」ということだ。どちらのキーワードも記述が必要だが、await演算子が含まれるメソッドにasync修飾子が付けられていないとコンパイル・エラーになるため、書き忘れることはないだろう*1

 そして、このasyncキーワードで修飾されたメソッドを「非同期メソッド」と呼ぶ。List 1は簡単な非同期メソッドの例だ。

*1 async修飾子が付けられているメソッドにawait演算子が含まれない場合は、「処理を待機しなければならない箇所がありませんが、これは意図した記述ですか?」に類する警告がされるだけで、コンパイル・エラーにはならない。

 

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

  Me.button.IsEnabled = False
  Await HeavyWork()  ' 何か重たい処理
  Me.button.IsEnabled = True

End Sub
List 1: 非同期メソッドの例(上:C#、下:VB)

 asyncキーワードは単にメソッドの修飾でしかなく、これを加えたことによってコンパイル結果として何かが展開されたりすることはない。であれば記述するのはawait演算子だけで十分で、async修飾子は不要だったのではないかと思うかもしれないが、それでも“async”の修飾を言語仕様として強制したのは、主にC# 4.0までのコードとの互換のためと言われている。言語設計の背景は「Asynchrony in C# 5 Part Six: Whither async?(英語)」で詳細に語られている。日本語訳もあるので、興味のある方は読んでみてほしい。

 メソッドに“async”という修飾があることと、名前が非同期メソッドであることから、「修飾されたメソッド全体が別スレッド上で非同期的に動作する」と思うかもしれない。しかし実際はそうではなく、「修飾されたメソッドには非同期処理を待つ必要がある制御フロー(await)が含まれており、非同期処理の完了後、再度そのメソッドの続きから始められるようにコンパイラに書き換えを指示する」というのがasync修飾子の持つ意味だ。実際の展開結果とは異なるが、意味としてはList 2のように書き換えられる。

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

  Me.button.IsEnabled = False
  ' UIに同期的な継続処理
  HeavyWork().ContinueWith(
    Function(_)
        Me.button.IsEnabled = True
    End Function,
    TaskScheduler.FromCurrentSynchronizationContext())

End Sub
List 2: 継続渡しへの変換(上:C#、下:VB)

 前回紹介したTAPと同じ形であることが分かるだろう。つまり非同期メソッドは、「ContinueWithメソッドを言語レベルでサポートしたもの」と言ってもよい。

await演算子

 await演算子は、async修飾子の付くメソッドの中で1つ以上記述できる。await演算子にはGetAwaiterメソッド(もしくは同名の拡張メソッド)を実装した“Awaitable”な型を渡すことができる*2。現在のところ、.NET Frameworkによって提供されている型の中でこの条件を満たしているのは、TaskクラスとTask<T>クラス(いずれもSystem.Threading.Tasks名前空間)の2つのみとなっている。

 “await”という名前から、「渡した非同期タスクが完了するまで、呼び出し元スレッドをブロックして待機する」というように感じるかもしれないが、実際はそうではない。await演算子の意味は、「待っているタスクがまだ完了していない場合、メソッドの残りをそのタスクの『継続』として登録して呼び出し元に処理を戻し、タスクが完了したら登録しておいた継続処理を実行すること」なので注意が必要だ。

*2 非同期メソッドは「Awaitableパターン」という仕組みに則って実現されている。

非同期メソッドの動き

 非同期メソッドの挙動をひと言で表すと、「メソッドを一時中断し、awaitされた処理の完了を待って残りの処理を再開する」だろう。しかし、こんな文章だけでは今ひとつピンと来なかったり、しっくり来なかったりしている方もいると思う。そこで、もう少しビジュアルに非同期メソッドの動きを解説して行くことにする。

 Figure 1、Figure 2は、非同期メソッドのソース・コードとスレッド間のやり取りを対応付けた図だ。

Figure 1: 非同期メソッドの例

 

Figure 2:スレッド間のやり取り

 ボタン・クリックのイベントが発生すると、UIスレッド上で の処理が実行される。最初のawait演算子が見つかるまでは通常の同期処理と何も変わらない。その後、に続けての処理が別スレッド上で開始される。Figure 2のとおり、このときUIスレッド上の処理はいったん終了してしまっている。つまりボタン・クリックのイベント・ハンドラは、を呼び出した段階でreturnするということだ。すでにお分かりのこととは思うが、が実行されている間はUIスレッドは解放されているため、別のユーザー操作に応答することができる。が完了すると、自動でUIスレッド上に処理が戻されたうえでが実行される*3

 awaitはこのように前後の処理を結び付けている。同じメソッド内に処理が書かれているが、実際には分離されていることを意識しておいてほしい。しかし、スレッド間の移動は全てコンパイラによって隠ぺいされるため*4、あたかも同期処理と同様に非同期処理を記述することができる。開発者に処理の分離を意識させることなく実現しているところに、非同期メソッドのすごさがある。

 ただし、必ずしも上記のような挙動を示すわけではない。の処理にかかる時間が極めて短い場合は、は分離されることなく連続的に実行される。滅多にないことかもしれないが、このようなケースもあることを覚えておいてほしい。

*3 awaitがメソッド内に複数ある場合は、このスレッド間のやり取りが繰り返されることになる。
*4 .NET Framework標準で提供されるTaskクラスに限る。await可能なクラスを自作する場合は開発者が担保しなければならない。

 続いて次のページでは、非同期メソッドが記述可能な場所や戻り値について説明する。

 

 INDEX
  連載:C# 5.0&VB 11.0新機能「async/await非同期メソッド」入門
  第2回 非同期メソッドの構文
  1.非同期メソッドのキーワード/非同期メソッドの動き
    2.任意の記述場所/戻り値
    3.例外処理/UIスレッドに制御を戻すかどうかをカスタマイズする

 

インデックス・ページヘ  「連載:C# 5.0&VB 11.0新機能「async/await非同期メソッド」入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH