MetroStyleApp入門 vol31.await / asyncについて

日曜日 , 8, 7月 2012 4 Comments

(この記事は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の出力が得られました。

 

 今回のサンプルのDLはこちら(85.3kb)

 

追記:コメントで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については知識不足で勉強することがいっぱいだ)

 


4 thoughts on “ : MetroStyleApp入門 vol31.await / asyncについて”
  • .NET Clips より:

    MetroStyleApp入門 vol31.await / asyncについて | 眠るシーラカンスと水底のプログラマー…

    素敵なエントリーの登録ありがとうございます – .NET Clipsからのトラックバック…

  • neuecc より:

    ここでいう「同期的」というのは、「同期のような非同期」でいいでしょうか?
    一番上の例のようなコードが勿論ベストなわけですが、
    それをメソッドに切り出すのならTask.Runで包むまず、そのままstringを返すべきかと思います。

    private async Task GetTaskC()
    {
    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 async Task 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());
    }

    です。
    こうすることで

    await GetA();
    await GetB();
    await GetC();

    と呼べます。

  • neuecc より:

    ああ、すみません、コメント内だとジェネリクスが消えるので、戻り値の型が変ですね。
    GetTaskCのほうの型ですが、[をジェネリクスの括弧に置き換えて読んでほしいのですが
    private async Task[string] GetTaskC()
    です。

  • coelacanth より:

    >neueccさん

    いつも勉強させてもらっております!!
    ご指摘ありがとうございます。
    確認後、ブログの内容も適切なものに修正させていただきます。

  • Please give us your valuable comment

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です