しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

非同期コントローラを使ってみた

ASP.NET MVC 2 で追加された非同期コントローラがあまりにも使われていなくて個人的には悲しい。

ネットワーク I/O の部分で使うといいと思うんですけど、今回はみにもばのサーバーを変更するついでに .NET 4 版の ASP.NET MVC 2 で動くようにして非同期コントローラをタイムライン取得部分で使うようにしました。

実際にやってみた

使い方は非常に簡単ですよ。まずは基本クラスを AsyncController に変更しましょう。

// 基本クラスを AsyncController に変更する
public class HomeController : AsyncController

Futures の時には MapAsyncRoute を使ってルーティング時に非同期アクションを宣言する必要がありましたが、MVC 2 では ActionNameAsync, ActionNameCompleted という命名規約を守るだけで非同期アクションとして実行させることが出来ます。

アノテーションを使うわけでもなく、命名規約を使うところがわかりやすくて個人的には気に入ってます。

// 同期アクション
public ActionResult Index() { ... }

// 非同期アクション
// 開始メソッド
public void IndexAsync() { ... }
// 完了メソッド
public ActionResult IndexCompleted() { ... }

同期アクションは今まで通りなので問題ないと思いますが、非同期アクションはメソッドを 2 つ用意する必要があります。これはイベントベースの非同期パターンと殆ど同じと考えてもらって良いと思います。

IndexAsync は実際の処理を行わずにすぐに処理を返す必要があります。一般的にはスレッドプールで処理させるか、非同期メソッドが用意されているコンポーネントを使うことになると思います。

とりあえず開始メソッドを見てもらいましょう。

public void IndexAsync()
{
    TwitterService twitter = new TwitterService();

    // 完了イベントを登録する
    twitter.GetTimelineCompleted += (sender, e) =>
    {
        // 結果をコレクションに追加する
        // ここに追加しておくと完了メソッドの引数に自動的にバインドされる
        AsyncManager.Parameters.Add("tweets", e.Result);

        // 保留中の操作数を 1 減らす
        AsyncManager.OutstandingOperations.Decrement();
    }

    // 操作数を 1 増やす
    AsyncManager.OutstandingOperations.Increment();

    // 非同期でタイムラインを取得開始
    twitter.GetTimelineAsync();
}

はい、また見慣れないものが出てきましたね。AsyncManager は非同期操作を管理するためのクラスで、このプロパティ経由でインスタンスを取れます。非同期コントローラの処理は AsyncManager が最重要と言っても過言ではないぐらい重要になります。

AsyncManager.OutstandingOperations プロパティは保留中の操作の数を保持していて、このカウンタが 0 になった時に保留中の操作がすべて終了したと MVC ランタイムが判断して完了メソッドが呼ばれます。つまり、3 回非同期で処理を行う場合はカウンタを 3 つ増やして、処理が終わるたびに 1 つずつ減らしていけばすべての処理が終わり次第、自動的に完了メソッドが呼び出されるので ViewResult を返したりできるわけです。

AsyncManager.Parameters プロパティは完了メソッドに渡すパラメータを保持していて、このコレクションにキーと値を追加すると完了メソッドのキーと同じ名前の引数に自動的にバインドされます。

最後に完了メソッドを見てもらいましょう。

// AsyncManager.Parameters コレクションから引数に値が自動的にバインドされる
public ActionResult IndexCompleted(List<Tweet> tweets)
{
    return View(tweets);
}

非常にシンプルですが引数に注目してください。完了メソッドはランタイムから呼び出されますが、AsymcManager.Parameters にキーと値を追加しておくとキーと同じ名前の引数に値が自動的にバインドされます。これでビューに値を渡すことが出来ますね!

注意点

非同期コントローラは同期コントローラに比べて複雑な処理が入るためオーバーヘッドが大きくなります。したがって、どのアクションを非同期で処理を行うか、注意深く見極めることが重要です。

基本的な利用シナリオとしては複数のネットワーク I/O による待ち時間が発生する場面だと思われます。非同期の処理に対応している FW はかなり少ないと思うので、こういった場面では ASP.NET MVC 2 の非同期コントローラを使う価値は十分にあるのではないでしょうか。