ASP.Net のページライフサイクルと、Session による排他処理
ASP.NET のイベント発生タイミング(ライフサイクル)と、Session による排他処理の実装方法について、実験してみました。
合わせて、Session の Abandon や RemoveAll の挙動も確認。
環境
- 開発環境
- Visual Studio 2008 Standard:
- 動作環境
- NET Framework 2.0:
- 言語
- Visual Basicにて、ASP.NET Webサイトを作成:
ページのライフサイクル(イベントが呼び出される順番)
Webフォーム(System.Web.UI.Page)の、ライフサイクルについて、実験してみました。
参考サイト
確認方法
Pageのイベントハンドラ関数を、各イベント分用意し、Debug.WriteLine にてVisualStudioの出力ウインドウへ表示。
ソース
Public Partial Class LifeCycle Inherits System.Web.UI.Page Public Sub New() Debug.WriteLine("New") End Sub Protected Overrides Sub Finalize() Debug.WriteLine("Finalize") MyBase.Finalize() End Sub Private Sub LifeCycle_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed Debug.WriteLine("Disposed") End Sub Private Sub LifeCycle_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init Debug.WriteLine("Init") End Sub Private Sub LifeCycle_InitComplete(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.InitComplete Debug.WriteLine("InitComplete") End Sub Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load Debug.WriteLine("Load") End Sub < − 中略 − > End Class
結果
ライフサイクルは、次の順でした。
出力ウインドウへ表示された順 | 実装したイベント |
---|---|
1 | New |
2 | Page.PreInit |
3 | Page.Init |
4 | Page.InitComplete |
5 | Page.PreLoad |
6 | Page.Load |
7 | Page.LoadComplete |
8 | Page.PreRender |
9 | Page.PreRenderComplete |
10 | Page.SaveStateComplete |
11 | Page.Unload |
- (表示されず) | Page.Disposed |
- (表示されず) | Finalize |
Disposed と、Finalize については、取得できませんでした。
参考サイト(ASP.NET ページのライフ サイクルの概要)の、「ライフ サイクルのイベント」を見ると、記載が無いので、拾えないのか、実行されていないのか、実装するのが問題なのか、分かりませんでした。
SessionIDの確定方法
SessionIDは、未確定時にリロードすると、都度、異なるSessionIDが取得されます。Sessionによる排他が必要な場合は、SessionIDを確定しておく必要があります。
SessionIDが確定する方法(メソッド)について、実験してみました。
確認方法
確認用ページを作成し、Sessionの挙動を確認。
結果
SessionIDが確定するメソッドと、タイミングは、次のとおりでした。
操作目的 | 操作 | Session.Mode | SessionIDの確定 |
---|---|---|---|
初期化 | Session.Abandon() | InProc | 確定しない |
StateServer | 確定しない | ||
Session.Clear() | InProc | 確定する | |
StateServer | 確定する | ||
Session.RemoveAll() | InProc | 確定する | |
StateServer | 確定する | ||
Session.Remove(SESSION_KEY) | InProc | 確定する | |
StateServer | 確定する | ||
確認 | Session(SESSION_KEY) Is Nothing | InProc | 確定しない |
StateServer | 確定しない | ||
Session.KeysをFor Each | InProc | 確定しない | |
StateServer | 確定しない | ||
設定 | Session(SESSION_KEY) = 1 | InProc | 確定する |
StateServer | 確定する |
Sessionによる排他処理が必要な場合は、排他処理を行う前の画面で、Session値を設定することで、SessionIDを確定するのが良いと思われます。
なお、確定したSessionIDを未確定にはできないようです。(
同一SessionIDで、処理の排他ロック
同一SessionIDで、同時に処理が実行された場合の挙動について、実験してみました。
参考サイト
- ASP.NET Session State Overview | Microsoft Docs
- 「現在の要求とセッション状態」に、排他について記述あり
実験方法
フォームにボタンを設置し、そのボタンのクリックイベントにて、Sessionの値を更新するよう実装。
フォームの各イベントに、実行時間と、イベント名を出力するよう実装。
フォームのイニシャライズ(New) 完了が、現在時刻の5秒ごとになるよう実装。(同時実行を再現する目的)
それぞれの結果を取得する為、ウインドウを2つ開き、SessionIDが同じであることを確認する。
結果
ウインドウ A | ウインドウ B |
---|---|
[14:05:02.206]New [14:05:05.003]New完了。 [14:05:05.003]Me.PreInit 開始 [14:05:08.003]Me.PreInit 終了 [14:05:08.003]Me.Init 開始 [14:05:11.003]Me.Init 終了 [14:05:11.003]Me.InitComplete 開始 [14:05:14.003]Me.InitComplete 終了 [14:05:14.003]Me.PreLoad 開始 [14:05:17.003]Me.PreLoad 終了 [14:05:17.003]Me.Load 開始 [14:05:17.003]処理開始 [14:05:17.003]IsSynchronized:False [14:05:17.003]0 -> 1 [14:05:17.003]SessionにSet [14:05:37.002]処理終了 [14:05:37.002]Me.PreRenderComplete 開始 [14:05:40.002]Me.PreRenderComplete 終了 |
[14:05:03.362]New [14:05:05.003]New完了。 [14:06:10.017]Me.PreInit 開始 [14:06:13.017]Me.PreInit 終了 [14:06:13.017]Me.Init 開始 [14:06:16.017]Me.Init 終了 [14:06:16.017]Me.InitComplete 開始 [14:06:19.017]Me.InitComplete 終了 [14:06:19.017]Me.PreLoad 開始 [14:06:22.017]Me.PreLoad 終了 [14:06:22.017]Me.Load 開始 [14:06:22.017]処理開始 [14:06:22.017]IsSynchronized:False [14:06:22.017]1 -> 2 [14:06:22.017]SessionにSet [14:06:42.017]処理終了 [14:06:42.017]Me.PreRenderComplete 開始 [14:06:45.016]Me.PreRenderComplete 終了 |
結果を見ると、次のように動作しているようです。
- System.Web.UI.Page クラスのイニシャライズは、要求後に実行されている。
- PreInit 以降の処理は、Sessionによる排他ロック制御されている。
- VisualStudioへDebug.Writeで出力すると、Unloadまでは、ロックされている。
SessionIDが確定していれば、PreInit以降は、シリアルに処理してくれるようです。
まとめ
- SessionID単位で、排他処理が必要な場合
- 前ページなどでSession変数へ「処理前」フラグを設定
- 排他処理が必要なページの PreInit や Init で、Session変数が「処理前」フラグになっているかチェック
- なっていれば、処理
- なっていなければ、エラーページなどへ遷移させる
以上で、Sessionによる排他処理が実装できそう。
注意点としては、排他処理中に、後からリクエストされた分は、処理が終わるまで待つ(排他待ち)しかなさそう。
理由としては、イニシャライズ時にSessionの値が取得できれば、排他待ちは発生しない(切り分け、エラー表示等ができる)と思われるが、Sessionが扱えるのは、PreInit以降。
HTTPHandler絡みの制約ですかねぇ。