(この記事はWindows 8 RP、VisualStudio2012RCで確認されています)
await / async構文は非同期処理をあたかも同期処理のフローのように記述できるため非常に便利な構文ですが、やっぱりちゃんと理解してないとはまります。
例えば非同期処理の呼び出しを順序立てて行う必要がある場合。
実験のために応答時間の異なる3つのAPI(ともいえないもの)を用意します。
・http://coelacanth.heteml.jp/win8/api_test/a.php
応答に3秒かかります。aを出力します。
・http://coelacanth.heteml.jp/win8/api_test/b.php
ほぼ即座に応答します。bを出力します。
・http://coelacanth.heteml.jp/win8/api_test/c.php
応答に2秒かかります。cを出力します。
まずはシンプルに以下のようなコードを実行します。
var http = new HttpClient(); Uri uri = new Uri("http://coelacanth.heteml.jp/win8/api_test/a.php"); var response = await http.GetAsync(uri); System.Diagnostics.Debug.WriteLine(await response.Content.ReadAsStringAsync()); var http2 = new HttpClient(); Uri uri2 = new Uri("http://coelacanth.heteml.jp/win8/api_test/b.php"); var response2 = await http2.GetAsync(uri2); System.Diagnostics.Debug.WriteLine(await response2.Content.ReadAsStringAsync()); var http3 = new HttpClient(); Uri uri3 = new Uri("http://coelacanth.heteml.jp/win8/api_test/c.php"); var response3 = await http3.GetAsync(uri3); System.Diagnostics.Debug.WriteLine(await response3.Content.ReadAsStringAsync());
このコードを実行するとa→b→cという結果を得られます。呼び出しが同期的に行われていることがわかります。
続いて各呼び出しを関数として切り出します。
this.getA(); this.getB(); this.getC();
private async void getC() { var http = new HttpClient(); Uri uri = new Uri("http://coelacanth.heteml.jp/win8/api_test/c.php"); var response = await http.GetAsync(uri); System.Diagnostics.Debug.WriteLine(await response.Content.ReadAsStringAsync()); } private async void getB() { var http = new HttpClient(); Uri uri = new Uri("http://coelacanth.heteml.jp/win8/api_test/b.php"); var response = await http.GetAsync(uri); System.Diagnostics.Debug.WriteLine(await response.Content.ReadAsStringAsync()); } private async void getA() { var http = new HttpClient(); Uri uri = new Uri("http://coelacanth.heteml.jp/win8/api_test/a.php"); var response = await http.GetAsync(uri); System.Diagnostics.Debug.WriteLine(await response.Content.ReadAsStringAsync()); }
予想通りこの呼び出しは順次実行されません。得られる出力は以下です。
b→c→c
まさしく非同期な結果ですね。
では同期的に呼び出したい場合(awaitをつけて呼び出したい場合)どのように書くかというと以下のようにTaskを利用します。
var a = await this.getTaskA(); System.Diagnostics.Debug.WriteLine(a); var b = await this.getTaskB(); System.Diagnostics.Debug.WriteLine(b); var c = await this.getTaskC(); System.Diagnostics.Debug.WriteLine(c);
private Task<string> getTaskC() { return Task.Run(async () => { var http = new HttpClient(); Uri uri = new Uri("http://coelacanth.heteml.jp/win8/api_test/c.php"); var response = await http.GetAsync(uri); string str = await response.Content.ReadAsStringAsync(); return str; } ); } private Task<string> getTaskB() { return Task.Run(async () => { var http = new HttpClient(); Uri uri = new Uri("http://coelacanth.heteml.jp/win8/api_test/b.php"); var response = await http.GetAsync(uri); string str = await response.Content.ReadAsStringAsync(); return str; } ); } private Task<string> getTaskA() { return Task.Run(async () => { var http = new HttpClient(); Uri uri = new Uri("http://coelacanth.heteml.jp/win8/api_test/a.php"); var response = await http.GetAsync(uri); string str = await response.Content.ReadAsStringAsync(); return str; } ); }
これでa→b→cの出力が得られました。
追記:コメントでneueccさんに指摘いただきました。
private async Task getTaskA() { var http = new HttpClient(); Uri uri = new Uri("http://coelacanth.heteml.jp/win8/api_test/a.php"); var response = await http.GetAsync(uri); string str = await response.Content.ReadAsStringAsync(); System.Diagnostics.Debug.WriteLine(str); return; }
この書き方だと、呼び出した側にawaitが来て、
await this.getTaskA();
勉強になります!!
(まだawait / asyncやTaskについては知識不足で勉強することがいっぱいだ)
Please give us your valuable comment