Asp.Net在多線程環境下的狀態存儲問題


      在應用開發中,我們經常需要設置一些上下文(Context)信息,這些上下文信息一般基於當前的會話(Session),比如當前登錄用戶的個人信息;或者基於當前方法調用棧,比如在同一個調用中涉及的多個層次之間數據。

      在.Net中,常用的有以下三種方法來實現這個特性.

      HttpContext.Current.Session或HttpContext.Currnet.Items是大家使用的最多的方式.

      [ThreadStatic]方式可以存儲單個線程的共享狀態.

      System.Runtime.Remoting.Messaging.CallContext類則可以存儲一個邏輯線程的共享狀態,即主線程和其所有子線程都共享這段內存.

      在Asp.Net中通常使用第一種方式.但是魚李寫了一篇文章,指出HttpContext.Current並非無處不在,只有是由請求發起的線程,HttpContext.Current才不為空.換句話說,在多線程環境下, 比如是由定時器發起的線程,Currnet屬性就為空,這時依賴於它的相關功能便無法完成.比如使用它獲取文件路徑等.

      魚李給出了兩種解決方法,將HttpContext.Current保存在外部變量中,或者通過函數參數傳遞.

      我個人認為這只是折中的解決辦法,它沒有在解決問題的同時保待簡單性,即程序員不需關心HttpContext.Current的保存位置與傳遞方式,而直接便可使用.

      后台又查到了A大的一篇文章.給出了保存下文(Context)信息的通用解決方案,簡單來說,在桌面環境使用System.Runtime.Remoting.Messaging.CallContext類,在Web環境下使用常規的HttpContext.Current,但是同時在System.Runtime.Remoting.Messaging.CallContext類中也保存了一個對它的引用.在線程切換時,依照.Net的設計,System.Runtime.Remoting.Messaging.CallContext類中保存的實現了ILogicalThreadAffinative接口的數據都會自動被復制到新的線程中,即完成了上下文傳遞.

      A大給出了一個精妙的解決方案,但卻沒有解決我所有的疑問,比如48L的園友就提出了"請教一下,callcontxt無論是那種應用都可以使用,為什么還要使用HttpSessionState?".於是我繼續探究.終於在一篇老外的博文中找到了答案.

      在那篇文章里,老外做了一個試驗.有兩個頁面,均在其構造函數與Page_Load中使用上面三種方式記錄當前線程Id,但是在名為slow的頁面中人為讓處理線程睡一下來模擬耗時操作.首先訪問slow頁面,在其返回前快速多次刷新fast頁面.最終的打印結果讓作者surprise了一下.對於slow頁面,執行構造函數的線程與Page_Load的線程保持一致,三種方式的記錄結果也沒有丟失,但是在fast頁面中,有可能出現執行構造函數的線程與Page_Load的線程不一致,三種方式的記錄結果也丟失了兩種:僅剩下HttpContext了.

      我的重現代碼如下,增加了LogicalSetData方式,后文再表:

public partial class Fast : System.Web.UI.Page
{
    [ThreadStatic]
    private static string info = string.Empty;

    public Fast()
    {
        info = "fast ctor:" + Thread.CurrentThread.ManagedThreadId;
        CallContext.SetData("id", Thread.CurrentThread.ManagedThreadId);
        CallContext.LogicalSetData("id1", Thread.CurrentThread.ManagedThreadId);
        Items["id2"] = Thread.CurrentThread.ManagedThreadId;
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Write("ThreadStatic:" + info);
        info = string.Empty;
        Response.Write("<br />");
        Response.Write("CallContext.SetData:" + CallContext.GetData("id"));
        Response.Write("<br />");
        Response.Write("CallContext.LogicalSetData:" + CallContext.LogicalGetData("id1"));
        Response.Write("<br />");
        Response.Write("Items:" + Items["id2"]);
        Response.Write("<br />");
        Response.Write("<br />fast page_load:" + Thread.CurrentThread.ManagedThreadId);
    }
}

public partial class Slow : System.Web.UI.Page
{
    [ThreadStatic]
    private static string info = string.Empty;

    public Slow()
    {
        Thread.Sleep(1000);
        info = "slow ctor:" + Thread.CurrentThread.ManagedThreadId;
        CallContext.SetData("id", Thread.CurrentThread.ManagedThreadId);
        CallContext.LogicalSetData("id1", Thread.CurrentThread.ManagedThreadId);
        Items["id2"] = Thread.CurrentThread.ManagedThreadId;
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        Thread.Sleep(1000);
        Response.Write("ThreadStatic:" + info);
        info = string.Empty;
        Response.Write("<br />");
        Response.Write("CallContext.SetData:" + CallContext.GetData("id"));
        Response.Write("<br />");
        Response.Write("CallContext.LogicalSetData:" + CallContext.LogicalGetData("id1"));
        Response.Write("<br />");
        Response.Write("Items:" + Items["id2"]);
        Response.Write("<br />");
        Response.Write("<br />slow page_load:" + Thread.CurrentThread.ManagedThreadId);
    }
}

 

      執行結果如下:

      從博文中獲知,這是完全正常的執行結果, Asp.Net開發團隊還為其起了個好聽的名字:Thread-Agile.這個特性表明,即使你使用常規的Asp.Net開發方式,也不能保證所有的代碼一定會在同一線程中執行.從其它的文章中獲知,這是與負載有關的.負載越大,越有可能產生多線程.每當程序進入一個新的線程中執行時,Asp.Net會手動(是使用額外代碼實現的,不是.Net自帶的機制)將HttpContext對象復制到新線程中.一方面這能將多線程完全透明,讓程序員使用單線程的編程方式編寫多線程程序;另一方面CallContext.SetData與[ThreadStatic]就丟失了.但由於使用LogicalSetData方法存儲的數據其內部都會自動封裝成實現了ILogicalThreadAffinative接口的對象,所以在線程切換時能正常流轉.

      所以,在Asp.Net環境下,除非自己建立一套上下文環境解決方案,否則在該用的情況下還是老老實實使用HttpContext吧.

      歡迎各路朋友指正.

 

      參考

      HttpContext.Current並非無處不在

      如何實現對上下文(Context)數據的統一管理 [提供源代碼下載]

      CallContext和多線程

      HTTPContext across threads

      Do ASP.NET Requests always BeginRequest and EndRequest on the same thread?

      CallContext vs ThreadStatic

      ThreadStatic, CallContext and HttpContext in ASP.Net

      CallContext vs. ThreadStatic vs. HttpContext

      關於線程及CallContext

      多線程編程之計算限制型異步操作

      CallContext.LogicalGetData Vs. CallContext.GetData


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM