ASP.NET 2.0 のころから 非同期処理の方法が用意されていましたが、 ASP.NET 4.5 からASP.NET Web フォームの処理で時間のかかるタスクを非同期で処理する方法がより簡単になりました。今回は 非同期 Webフォームの実装方法の簡単な覚書を記載します。ASP.NET 4.0 以前の非同期ページの実装方法に関しては以下のURLが参考になります。下記URLには非同期ハンドラや非同期モジュールの実装方法も紹介されていて参考になります。

ASP.NET の非同期プログラミングを使ったスケール変換可能なアプリケーション
http://msdn.microsoft.com/ja-jp/magazine/cc163463.aspx

動作検証環境は次の通りです

  • Visual Studio 2012,  .NET 4.5
  • Windows Server 2012

1.非同期フォームを実装する

まず、非同期実行するフォーム(aspx)のページディレクティブに Async="true" を設定します。非同期処理の時間にタイムアウトを設定したい場合は AsyncTimeout を設定します。既定で45秒になっています。例えば次のようなディレクティブになります。CodeBehindなど環境によって異なる内容は適宜読み替えてください。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApi.WebForm1" Async="true" AsyncTimeout="45" %>

次のコードビハインドファイルを編集します。サンプルとして次のようなコードビハインドファイルを作成しました。内容自体はとくに大きな意味はありません。 Page_Init メソッド内で、 RegisterAsyncTask メソッドを使用して非同期実行するタスクを登録します。ここで登録されたタスク(メソッドの処理)はバックグラウンドで実行されます。PreRenderイベントまでASP.NETのイベント処理に到達した状態で非同期処理が完了していない場合は非同期処理完了まで処理が待機されるため非同期メソッド内からWebコントロールに問題なくアクセスすることができます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApi
{
    /// <summary>
    /// 参考ページ
    /// http://www.asp.net/web-forms/tutorials/aspnet-45/using-asynchronous-methods-in-aspnet-45
    /// RegisterAsyncTask で登録された非同期処理はPreRenderイベントまでページのイベント処理をブロック
    /// せずに別のスレッドで実行されます。PreRenderイベントに到達した状態で 非同期処理が完了していない
    /// と非同期処理の完了を待機します。この動作により、Webサーバーコントロールによるマークアップの出力
    /// は行われていないため、非同期タスクの処理中にWebサーバーコントロールにデータバインドすることができます。
    /// </summary>
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Init(object sender, EventArgs e)
        {
            // 非同期タスクの登録.
            RegisterAsyncTask(new PageAsyncTask(LongRunningWorkAsync));
        }

        private async Task LongRunningWorkAsync(CancellationToken cancelToken)
        {
            using (var client = new HttpClient())
            {
                string rss = await client.GetStringAsync("http://rss.rssad.jp/rss/itmatmarkit/rss.xml");
                TextBox1.Text = rss;
            }
        }
        private void Page_Error(object sender, EventArgs e)
        {
            Exception exc = Server.GetLastError();
            if (exc is TimeoutException)
            {
                // 非同期タスクでタイムアウトが発生した場合
                throw exc;
            }
        }
        protected void Page_Load(object sender, EventArgs e)
        {

        }
    }
}

上記サンプルプログラムでわかるように外部Webサービスにアクセスするようなシーケンシャルに実行すると時間のかかる処理をマルチスレッドで非同期に実行することでリクエストの処理時間を短くすることができるようになります。また非同期処理の実装も非常に簡単であることがわかります。

2.まとめ

簡単ですが説明は以上です。各メソッドやディレクティブの詳細な説明はMSDNを参照してください。また、本記事では紹介しませんでしたが、HttpTaskAsyncHandler, EventHandlerTaskAsyncHelper を使って非同期ハンドラや非同期モジュールを実装することもできます。詳細に関してはMSDNをご参照ください。