async と await の基本的な使い方
非同期プログラミングを行いたい状況は、ある処理に時間がかかることが想定されるところです。
ある処理に時間がかかるのに、他の処理も先に進めておきたい、ということが非同期で処理を行う動機ですね。
通常は早く処理がおわるけれども、時々遅くなるかもしれない処理、ということで真っ先に思いつくもののひとつは、 インターネットを経由したデータのダウンロードではないでしょうか。
ここでは、.NET Framework の WebClient クラスに新しく実装されたメソッドを使って、 async と await を用いた非同期プログラミングの実装方法をみてみましょう。
ちなみに、そもそも TAP とは何か?新しい非同期プログラミングって何?という方は、次の記事も参考になると思います。
» タスクベース・非同期パターン: async と await による非同期プログラミングとは
WebClient に実装された DownloadStringTaskAsync メソッド
非同期プログラミングを見る前に、同期プログラミングをみてみましょう。
テストを行うために次のようなプログラムを作りました。
テキストボックスがひとつと、ボタンが三つあります。テキストボックスには URL を入力して、 ボタンを押すと、その URL に HTTP の GET リクエストを投げて、結果を取得します。
結果はデバッグトレースに出力します。
ボタンが三つあるうち、一番上のボタンが WebClient の同期メソッドを利用して、残りの二つが WebClient の非同期メソッド、 特に DownloadStringTaskAsync を利用します。
では、早速、同期バージョンからみてみましょう。
同期メソッド
WebClient の文字列ダウンロードの非同期メソッドは DownloadString です。
ボタンのイベントハンドラは次のように書きました。キャッシュを使われると実験がうまくいかないのでキャッシュを使わないように、キャッシュポリシーを設定しています。
private void goButtonSync_Click(object sender, EventArgs e)
{
var client = new WebClient();
client.CachePolicy =
new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
Debug.WriteLine("--- Sync Begin ---");
var s = client.DownloadString(urlTextBox.Text);
Debug.WriteLine(s);
Debug.WriteLine("--- Sync End ---");
}
これを実行すると、次のようにデバッグ出力に記録されます。
--- Sync Begin --- <!doctype html><html itemscope="itemscope" itemtype="http://sch ... (途中省略) ... </script></body></html> --- Sync End ---
Begin ... 結果 ... End という風に順序良く記録されています。
非同期メソッド DownloadStringTaskAsync
次に非同期メソッド DownloadStringTaskAsync を使うと次のようになります。
private void goButtonAsync_Click(object sender, EventArgs e)
{
var client = new WebClient();
client.CachePolicy =
new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
Debug.WriteLine("--- Async Begin ---");
var t = client.DownloadStringTaskAsync(urlTextBox.Text);
Debug.WriteLine(t);
Debug.WriteLine("--- Asnc End ---");
}
デバッグ出力にはボタンを押すと直ちに次のように記録されました。
--- Async Begin --- System.Threading.Tasks.Task`1[System.String] --- Asnc End ---
直ちにメソッド呼び出しが返るのはいいのですが、残念ながら肝心のダウンロードされたものが取得できていません。
DownloadStringTaskAsync メソッドの戻り値は System.Threading.Tasks.Task<string> 型のオブジェクトが返されています。
では、結果を取得するにはどうしたらよいでしょうか。
C# 5.0 の非同期プログラミングの方法では、次のように await を使って、処理を待つ場所を指定します。そして、 await を利用するメソッドには async をつけます。
private async void goButtonAsync2_Click(object sender, EventArgs e)
{
var client = new WebClient();
client.CachePolicy =
new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
Debug.WriteLine("--- Async Begin ---");
Task<string> t = client.DownloadStringTaskAsync(urlTextBox.Text);
Debug.WriteLine(t);
Debug.WriteLine("--- Asnc End ---");
Debug.WriteLine("--- Await Begin ---");
var s = await t;
Debug.WriteLine(s);
Debug.WriteLine("--- Await End ---");
}
これを実行すると、次のように出力されます。
--- Async Begin --- System.Threading.Tasks.Task`1[System.String] --- Asnc End --- --- Await Begin --- <!doctype html><html itemscope="itemscope" ... (途中省略) ... </script></body></html> --- Await End ---
この結果から、DownloadStringTaskAsync メソッドは直ちに返り、その後、await Task の箇所で処理の終了を待ったことがわかります。
また、変数 s には結果となる文字列 (string) が入っていることも分かりますね。
ちなみに、ここでは、Debug.WriteLine がわずらわしくて複雑そうにみえているかもしれませんが、それを省くと、次のようなシンプルなコードになります。
private async void goButtonAsync2_Click(object sender, EventArgs e)
{
var client = new WebClient();
client.CachePolicy =
new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
Task<string> t = client.DownloadStringTaskAsync(urlTextBox.Text);
var s = await t;
}
また、一度 Task を受け取らずに次のようにいきなりメソッド呼び出しに await をつけても OK です。
簡単に非同期呼び出しを利用できることがわかりますね。
注意したいのは、この場合、ボタンのイベントハンドラ自体が非同期メソッドであるということです。
await をつけてメソッド内の処理をブロックしても、このイベントハンドラ自体はブロックされていません。つまり、UI はフリーズせずにボタンを連打できるような状況です。
それがイベントハンドラに async を付けたということです。
従って、これによってイベントハンドラ内部で非常に容易に非同期処理が記述できることになるのです。