背景
Windows アプリケーションの場合、長い処理中に「お待ちください」画面を表示することは、比較的簡単です。一番簡単で乱暴な方法は、長い処理の所々でメッセージ処理をさせるようにすることです。
ところが、ASP.NET では、少しひねりが要ります。まず、ASP.NET でコーディングするコードは、サーバで実行されており、ユーザが見ているところではありません。このため、サーバでの処理が一定時間以上経過すると、アプリケーションの設定やブラウザによって、「サーバからの応答がない」と判断されてしまう、ということです。このことに注意しながら、「お待ちください」画面を表示する方法を紹介します。
なお、ここで紹介する方法は、patterns & practices > 付録 B : 実践サンプル集 を元に作成しました。
前提条件
- メモリ量が十分であること
- 同時接続数が少ない、または同時に「お待ちください」を表示する場合が少ないこと
コード
長い処理をするクラス。ここでは LongLongWait クラスとして、指定された回数、1秒スリープを実行しています。
#using System.Threading;
public sealed class LongLongWaiting {
private readonly long _waitSec;
private string _progress;
private Thread _thread;
///
/// 指定時間待機する処理を行うクラスのコンストラクタです。
///
/// 待機する秒数
public LongLongWaiting(long waitSec) {
this._waitSec = waitSec;
this._progress = string.Empty;
this._thread = null;
}
///
/// 進捗状況を現す文字列を取得します。
///
public string Progress {
get {
return this._progress;
}
}
///
/// 実行中かどうかを検査します。
///
public bool IsRunning {
get {
return (this._thread != null);
}
}
///
/// 指定時間待つ動作を開始します。
///
public void wait() {
this.wait("");
}
///
/// 指定時間待つ動作を開始します。
///
/// スレッドに付ける名前を指定します。
public void wait(string threadName) {
if (this._thread == null) {
this._thread = new Thread(new ThreadStart(this.execute));
this._thread.Name = threadName;
this._progress = string.Empty;
this._thread.Start();
}
}
///
/// スレッドにて行われる、実際の処理です。
///
private void execute() {
try {
for (long sec = 0; sec < this._waitSec; sec++) {
System.Threading.Thread.Sleep(1000);
this._progress = string.Format("{0}/{1} 経過", sec + 1, this._waitSec);
}
} catch (Exception e) {
this._progress = e.Message;
} finally {
this._thread = null;
}
}
}
長い処理を開始する画面。ここでは、以下のコントロールがあることを前提とします。
- 待機する秒数を入力する、TextBox
- ポストバックを発生させるための、Button
サーバコード
private void Button1_Click(object sender, System.EventArgs e) {
LongLongWaiting wait = new LongLongWaiting(long.Parse(this.TextBox1.Text));
Stocker.Add(this.Session.SessionID, wait);
wait.wait(this.Session.SessionID);
this.Response.Redirect(「お待ちください」画面の URL);
}
「お待ちください」画面。ここでは経過を示す文字列を表示するようにしています。また、最初の画面に戻る為にボタンを用意しています。
サーバコード
private void Page_Load(object sender, System.EventArgs e)
{
LongLongWaiting w = Stocker.GetValue(this.Session.SessionID) as LongLongWaiting;
if (w == null) {
this.Response.Redirect(エラー画面など);
}
if (w.IsRunning) {
this.Response.AddHeader("refresh", "2");
this.Label1.Text = w.Progress;
this.Button1.Enabled = false;
} else {
this.Label1.Text = "終了しました。";
Stocker.Remove(this.Session.SessionID);
this.Button1.Enabled = true;
}
}
実行中の内容をストックしておくクラス。
#using System.Collections;
public sealed class Stocker {
///
/// 結果を格納します。
///
private static System.Collections.Hashtable progresses = new System.Collections.Hashtable();
///
/// コンストラクタを隠します。
///
private Stocker() {
}
public static void Add(string key, object value) {
Hashtable.Synchronized(Stocker.progresses).Add(key, value);
}
public static bool Contains(string key) {
return Hashtable.Synchronized(Stocker.progresses).Contains(key);
}
public static void Remove(string key) {
Hashtable.Synchronized(Stocker.progresses).Remove(key);
}
public static object GetValue(string key) {
return Hashtable.Synchronized(Stocker.progresses)[key];
}
}
説明
まず、LongLongWait クラスからです。
ここでは、後に応用として中断することが出来るように、スレッド処理を行うクラスとして定義しています。また、スレッドクラスのインスタンスも保存しておくようにしています。
実際に呼び出されるメソッドは、ThreadStart デリゲート の宣言と一致する必要があります。したがって、引数を渡せないため、実行に必要な、外部から供給しなければならない値は、プロパティなどで通知しておきます。
実際にワーカースレッドで処理されるのが、execute メソッドです。ここでは処理していませんが、ThreadAbortException クラス をキャッチして、何らかの都合によるスレッドの中断も考慮するようにするべきでしょう。
次に、処理を開始する画面についてです。
ここでは、ボタンをクリックしたイベントにて、長い時間のかかる処理を実行します。ここではスレッドに、セッション ID を名前として与えていますが、特に必要な処理ではありません。LongLongWait クラスのインスタンスを生成し、ストッカーにストックして、「お待ちください」画面にリダイレクトさせています。
次に、お待ちくださいというメッセージを表示する画面です。
ストッカーから、セッション ID をキーにして、LongLongWait クラスのインスタンスを取り出します。IsRunning プロパティにて、処理が続いているのか、終了しているのかを判別しています。続行中の場合は、レスポンスヘッダにリフレッシュコマンドを追加し、定期的にサーバに処理が戻ってくるようにしています。終了している場合は、最初の画面に戻るボタンを有効にしています。
最後に、実行中のスレッドをストックしておくクラスです。
このクラスは Hashtable クラス を利用して、キーによって値を登録しています。キーをセッション ID にすることで、それぞれのセッションで独立した値を保存できます。
ASP.NET では、クライアントからの要求はスレッドによって並列に処理されます。しかし、Hashtable クラス はスレッドアンセーフなので、Hashtable.Synchronized メソッド によって、スレッドを同期させています。このため、リフレッシュを頻繁に行うと、それぞれの処理が直列に処理されることになり、パフォーマンスの低下を招きます。
応用
ここに紹介したコードでは、中断できるようになっていません。処理の中断が必要であるなら、それ相応のコードを書く必要があります。
投稿日時 : 2005年11月18日 21:37