前幾天對於ASP.NET Session State Server傳輸時機的研究,其實是在舖路,想解答最近遇到的一個疑惑:

當ASP.NET使用Cache、Session、Application儲存Reference Type資料時,是否會因序列化/反序列化出現更新延遲,或是仍因物件都指向同一記憶體區塊而即修改即更新?

對Value Type(實值型別)與Reference Type(參考型別)定義還有疑惑的朋友,可先參考MSDN文件及相關文章,有興趣還可以挑戰自我檢測,當成繼續往下看的先修課程。

Reference Type與Session議題之所以值得討論,是因為觀念不明確就會對程式行為有錯誤預期,容易寫出有問題的程式或是在分析問題時被誤導。舉例來說,假設我們在網頁A宣告了一個Reference Type物件,例如: List<string>,並將它存入Session。在網頁B時,從Session中取出List<string>,並且新增一個字串到List<string>,但沒有將該List<string>再存回Session。之後跳到網頁C,由Session取出該List<string>,此時會不會看到剛才的網頁B新增的字串??

Session機制保存資料的方式(Session State Mode)有四種: InProc、StateServer、SQLServer、Custom。其中InProc是直接使用Web Application的記憶體,故存入Session時,原則上符合一般Reference Type的表現。寫入Session的是指標,其他程式由Session取出時得到的也是指標,都指向同一個Instance(或說是同一塊記憶體)。因此對該物件的寫入操作,會即時改變記憶體儲存內容,不必存入Session就會生效。

當選擇StateServer、SQLServer運作模式,資料被儲存在另一個程序的記憶體或SQL Server磁碟空間,便不可能只保存指標,物件需先經過序列化再透過網路傳輸送至StateServer或SQL保存;而由Session讀取時,則要先從StateServer或SQL讀取資料,再反序列化還原成物件存回記憶體。而由先前文章觀察所得,ASP.NET會在Request開始時將Session還原快取在記憶體中,執行結束後再回寫StateServer或SQL Server,而在這段期間,Session中Reference Type的行為跟InProc模式沒有兩樣。

至於Application、Cache等保存機制,原理也與InProc Session相仿,都是使用記憶體儲存Reference Type時,故行為也相同,在取出物件後,所有更新動作會立即影響儲存在Application/Cache的內容,不需要重新存入Application/Cache就會生效。

用以下的實驗驗證:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
 
public partial class RefTypeTest : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
        Response.Write("<li>SessionMode: " + Session.Mode.ToString());
        if (Request["mode"] == "set")
        {
            //建立三個List<string>
            var b1 = new List<string>();
            var b2 = new List<string>();
            var b3 = new List<string>();
            //存入Application, Session及Cache
            Application["T1"] = b1;
            Session["T2"] = b2;
            Cache.Remove("T3");
            Cache.Add("T3", b3, null, DateTime.Today.AddDays(1),
                System.Web.Caching.Cache.NoSlidingExpiration,
                System.Web.Caching.CacheItemPriority.High, null);
            //顯示資料筆數
            Response.Write(string.Format("<li>Before: A-{0}, S-{1}, C-{2}",
                b1.Count, b2.Count, b3.Count));
            //在List<string>加入字串
            b1.Add("A"); b2.Add("S"); b3.Add("C");
            //重新由Appication, Session, Cache取回物件
            List<string>
                a1 = Application["T1"] as List<string>,
                a2 = Session["T2"] as List<string>,
                a3 = Cache["T3"] as List<string>;
            //顯示資料筆數
            Response.Write(string.Format("<li>After: A-{0}, S-{1}, C-{2}",
                a1.Count, a2.Count, a3.Count));
            //在List<string>加入字串
            a1.Add("X"); a2.Add("X"); a3.Add("X");
        }
        else
        {
            List<string>
                a1 = Application["T1"] as List<string>,
                a2 = Session["T2"] as List<string>,
                a3 = Cache["T3"] as List<string>;
            //顯示內容
            Func<List<string>, string> display = (a) =>
            {
                if (a == null) return "n/a";
                return string.Join("/", a.ToArray());
            };
            //顯示資料筆數
            Response.Write(string.Format("<li>Read: A-{0}, S-{1}, C-{2}",
                display(a1), display(a2), display(a3)));
        }
        Response.End();
    }
}

執行結果:

RefTypeTest.aspx

SessionMode: InProc
Read: A-n/a, S-n/a, C-n/a

RefTypeTest.aspx?mode=set

SessionMode: InProc
Before: A-0, S-0, C-0
After: A-1, S-1, C-1

RefTypeTest.aspx

SessionMode: InProc
Read: A-A/X, S-S/X, C-C/X

如我們預期,由Application、Session、Cache讀出List<string>後,在集合中加入新元素但不重新寫回Application、Session、Cache。但下個Request讀取到的,會是已加入新元素的結果。

接著我們調成StateServer模式,結果依然相同! 原因在於ASP.NET會在Request一開始由StateServer一次讀入全部Session物件,接下來如同InProcess模式即寫即更新,網頁結束後再將最終結果寫回StateServer。

RefTypeTest.aspx

SessionMode: StateServer
Read: A-n/a, S-n/a, C-n/a

RefTypeTest.aspx?mode=set

SessionMode: StateServer
Before: A-0, S-0, C-0
After: A-1, S-1, C-1

RefTypeTest.aspx

SessionMode: StateServer
Read: A-A/X, S-S/X, C-C/X

【結論】

不管使用Application、Session、Cache保存Reference Type時,請想像如同以Dictionary<string, object>方式保存的情境,一旦更動物件內容,將會影響其他Request看到的物件內容。


Comments

# by alexyeh09@gmail.com

Good!

# by alexyeh09@gmail.com

Good!

# by 貓咪圓滾滾

有時候我真的不得不承認 我應該是不太適合訂閱魔人級高手的文章 哈哈

Post a comment