酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Windowsストアアプリでの非同期処理

結論としては、

Windowsストアアプリの非同期処理には await/asyncキーワードを使おう!

です。

Windowsストアアプリでの非同期処理

非同期プログラミングは、UIを持つアプリケーションを開発する上で抑えておかなければならない重要な要素のひとつです。 過去にデスクトップWindowsでなんらかのアプリケーションを使っている際に「応答なし」と表示されて、アプリがフリーズしたのかと心配した経験をお持ちの方は多いのではないでしょうか。

f:id:ch3cooh393:20150511154357g:plain

ユーザーとアプリケーションの対話に使われる「UIスレッド」は、コントロールの描画・ユーザー操作の受け付けまたは応答をする役割のスレッドです。このスレッド上で、Webリソースのダウンロードなどのネットワーク処理、ストレージへのファイル保存などのIO処理、画像ファイルのデコードなどのイメージ処理は、処理に時間がかかってしまいブロッキング(処理待ち)が発生してしまいます。

同期型のアプリケーションでは、1つの処理でブロックされるとアプリケーション全体がブロックされてしまいます。下図のようにブロッキングが発生してしまうと、新しいユーザーの操作を受け付けられなくなってしまいます。

f:id:ch3cooh393:20150511154500j:plain

特にディスプレイ上に表示することのできるアプリケーション(またはウィンドウ)が限られているスマートフォンアプリやWindowsストアアプリで、このような状態になると画面の再描画も実行されなくなってしまいユーザーは何もできなくなってしまいます。一般的に処理中を表すプログレスリングのアニメーションまで止まってしまうとユーザーはアプリケーションが処理中とは気付かずに、画面が再描画されない状態はつまり「固まった」「フリーズしてしまった」と考えてしまうかもしれません。

非同期処理を使用すると、下図のようにユーザーの操作に対してアプリケーションは応答し続けることができます。

f:id:ch3cooh393:20150511154554j:plain

非同期処理は、Threadを始めIAsyncResult・Taskなど、.NET Frameworkの旧バージョンでもその時々の時代にあった非同期処理を実現するためのAPIが提供されてきましたが、単純な処理だけならまだしも複数のスレッドを制御・管理する必要があり、どうしても複雑な実装にならざるを得ませんでした。

このような非同期処理を容易に実装できるようにと .NET Framework 4.5(C# 5.0)から導入された「asyncキーワードとawaitキーワード」(Visual Basicでは、AsyncキーワードとAwaitキーワードに相当)を紹介します。

asyncとawaitキーワードを使った非同期メソッドの作り方

await演算子は、Task型またはTask<TResult>型を戻り値とするタスクベースの非同期メソッドに対して適用することができます。

まずasync修飾子とawait演算子を使った非同期メソッドの例をみてみましょう。下記のサンプルコードは、ボタンがクリックされると、非同期的にWebリソースにアクセスしHTMLを文字列として取得します。

private void Button1_Click(object sender, RoutedEventArgs e)
{
    await GetWebPageAsync();
}

private async Task GetWebPageAsync()
{
    var client = new System.Net.Http.HttpClient();

    var uri = new Uri("http://example.com/...");

    Task<string> getStringTask = client.GetStringAsync(url);
    var text = await getStringTask;

    // ...省略
}

下図は、前述したサンプルコードGetWebPageAsyncメソッドの挙動のイメージです。await演算子にてメソッドが中断されタスクの実行を待っているのが分かります。

f:id:ch3cooh393:20140331120813j:plain

サンプルコードについて詳しく説明します。ユーザーがボタンをクリックするとClickイベントハンドラ内でasync修飾子を付与されたGetWebPageAsyncメソッドを実行します。

HttpClientクラスのGetStringAsyncメソッドが「Task」を返します。次にawait演算子をgetStringTaskに適用しているため、Webリソースからの文字列取得処理(getStringTask)が完了するまで、GetWebPageAsyncメソッドの実行が一旦中断されます。

中断されている間、スレッドの制御がGetWebPageAsyncメソッドの呼び出し元(UIスレッド)に返され、ユーザーからの入力を受け付けられるようになります。getStringTaskが完了すると、GetWebPageAsyncメソッドの実行が途中から再開されます。

asyncとawait

asyncとawaitキーワードを使った非同期メソッドの挙動のイメージを掴むことができたでしょうか。asyncとawaitキーワードを使用することで、コンパイラはこのようなスレッドの複雑な切り替えを隠蔽します。開発者は非同期処理を今までよりも容易に使うことができ、複雑な実装に頭を悩ませずに済むようになりました。

asyncとawaitキーワードに関してもう少し解説します。

asyncキーワード

async修飾子は、メソッド内でawait演算子を使用するということを明示するための修飾子です。メソッド、ラムダ式、または匿名メソッドが非同期であることを指定します。このasync修飾子が付与されているメソッドのことを「非同期メソッド」と呼びます。非同期メソッドの呼び出し元は、非同期メソッドの完了を待たずに次の処理を実行することが可能です。

多くの場合には呼び出し元ではawait演算子と対で使用されます。また、メソッド内でawait演算子が使われていない場合には同期メソッドとして扱われます。詳細については後述します。

awaitキーワード

await演算子は、待機中のタスクが完了するまでメソッドの実行を中断するために使用されます。await演算子を使用する非同期メソッドではasync修飾子を付与しておく必要があります。await式は実行されているスレッド自身をブロックするのではなく、前述した図のように待機する場所を登録しておきます。そのあと作業が完了すると登録しておいた箇所から継続されます。

大半の非同期メソッドは、TaskクラスTask<TResult>クラスなどの待機可能な戻り値を返します。await演算子は非同期メソッドからそれらを受け取ります。返されたタスクのプロパティには、実行結果などの情報が含まれており、await演算子はこれらのプロパティにアクセスします。

await演算子を含まない非同期メソッドの実行

非同期メソッドには、await演算子が通常1つ以上存在していると考えても良いのですが、メソッド内にawait演算子がなくてもコンパイルエラーにはなりません。async修飾子が付与されていてもawait演算子が使われていなければメソッドは、同期メソッド同様にスレッドが中断されることなく実行されます。

挙動の違いを確認することができるように、下記のサンプルコードを用意しました。ボタンがクリックされるとテスト的に実装された非同期メソッドが呼び出されます。

private async void Button1_Click(object sender, RoutedEventArgs e)
{
    var text = await WaitAsynchronouslyAsync();

    // var text = await WaitSynchronously();
}

public async Task<string> WaitAsynchronouslyAsync()
{
    await Task.Delay(10000); // 時間のかかる処理のつもり
    return "finished";
}

private async Task<string> WaitSynchronously()
{
    new Task(() => { }).Wait(1000); // 時間のかかる処理のつもり
    return "finished";
}

WaitAsynchronouslyAsyncメソッドは、await演算子を使ってTask.Delayメソッドが完了するのを待ちます。スレッドの制御は呼び出し元(ボタンのClickハンドラからの呼び出しですのでUIスレッド)に返されます。UIスレッドはWaitAsynchronouslyAsyncメソッドの実行中ブロッキングされていないので、ユーザー操作を受け付けることができます。

WaitSynchronouslyメソッドは、async修飾子を付与していますが、メソッド内にawait演算子がなくスレッドの制御を呼び出し元に返しません。WaitSynchronouslyメソッドの実行は同期的におこなわれるため、UIスレッドはブロッキングされ、ユーザー操作を受け付けられなくなります。

非同期メソッドの命名規則

前述した通り、Windowsランタイムではアプリケーションの応答性を高めるために沢山の非同期メソッドが用意されています。これらの「async修飾子 (Visual Basicの場合はAsync修飾子)」を持つメソッドの名前の末尾には慣例によって「Async」がついています。

参照