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を未確定にはできないようです。(を除く)レガシーASPの時は、Abandonすると、SessionIDが変わったような...

同一SessionIDで、処理の排他ロック

同一SessionIDで、同時に処理が実行された場合の挙動について、実験してみました。

参考サイト
実験方法

フォームにボタンを設置し、そのボタンのクリックイベントにて、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までは、ロックされている。


Sessionによる排他を、図にしてみました。

SessionIDが確定していれば、PreInit以降は、シリアルに処理してくれるようです。

まとめ

  • SessionID単位で、排他処理が必要な場合
    1. 前ページなどでSession変数へ「処理前」フラグを設定
    2. 排他処理が必要なページの PreInit や Init で、Session変数が「処理前」フラグになっているかチェック
      1. なっていれば、処理
      2. なっていなければ、エラーページなどへ遷移させる


以上で、Sessionによる排他処理が実装できそう。


注意点としては、排他処理中に、後からリクエストされた分は、処理が終わるまで待つ(排他待ち)しかなさそう。
理由としては、イニシャライズ時にSessionの値が取得できれば、排他待ちは発生しない(切り分け、エラー表示等ができる)と思われるが、Sessionが扱えるのは、PreInit以降。
HTTPHandler絡みの制約ですかねぇ。