(この記事は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