慎用System.Web.HttpContext.Current


  

 

  每當控制流離開頁面派生的Web表單上的代碼的時候,HttpContext類的靜態屬性Current可能是有用的。 使用這個屬性,我們可以獲取當前請求(Request),響應(Response),會話(Session,)和應用程序對象(Application objects)以及請求更多服務。 以下面的代碼為例。

private void Page_Load(object sender, System.EventArgs e)
{
   MyClass myClass = new MyClass();
   myClass.DoFoo();
}

class MyClass
{
   public void DoFoo()
   {
      HttpContext.Current.Response.Write("Doing Foo");
   }
}

  Context在同一個應用程序域中請求當前上下文的能力是強大的,但也可能被濫用。你可以從業務對象使用HttpContext.Current打破你的架構層的界限,並且很輕松地將類與ASP.NET結合,而Windows Forms和the Compact Framework PDA則無法應用於此場景。

  HttpContext.Current是如何找到上下文當前請求。 此外,它總是能找到當前請求? 例如,下面的代碼的行為會是什么?

private void Page_Load(object sender, System.EventArgs e)
{
   ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
}

public void DoWork(object state)
{
   HttpContext context = HttpContext.Current;
  context.Response.Write("Do Work");
}

 

  答:上面的代碼會生成一個System.NullReferenceException,因為HttpContext.Current返回null。從設計的角度來看,上面的代碼至少存在兩個問題,但是讓我們討論HttpContext.Current工作之前是如何工作的。

  快速查看反編譯器實現Current的屬性看起來像下面這樣。

public static HttpContext get_Current()
{ 
    return (CallContext.GetData("HtCt") as HttpContext); 
}

       CallContext是類似於方法調用的線程本地存儲區的專用集合對象,並提供對每個邏輯執行線程都唯一的數據槽。CallContext 為調用路徑提供數據槽。CallContext.SetData 和 CallContext.GetData 可用於管理應用程序代碼中的調用上下文槽。每一調用路徑都有唯一的數據槽;也就是說,調用路徑之間不共享狀態。這些數據槽是命名過的,名稱用於訪問數據槽,使用該名稱可以顯式地釋放數據槽。線程本地存儲是一個概念,其中在一個應用程序域中的每個邏輯線程都有一個唯一的數據槽,以保持特定於自身的數據。 線程不共享數據,一個線程不能修改本地數據到另一個線程中。ASP.NET中,選擇一個線程來執行傳入的請求后,在本地的線程存儲參考當前請求的上下文。現在,無論線程在哪執行(一個業務對象,數據訪問對象),上下文時時存在方便檢索。

 

  知道了上面我們可以聲明如下:如果在處理請求時,執行移動到不同的線程(通過QueueUserWorkItem,或異步委托,作為兩個例子),當前背景下HttpContext.Current將不知道如何檢索,將返回null。 你可能會認為解決這個問題的一種方法是將引用傳遞給工作線程,就像下面的例子。

 

private void Page_Load(object sender, System.EventArgs e) 
{ 
    WorkerClass2 worker = new WorkerClass2(); 
    ThreadPool.QueueUserWorkItem(new WaitCallback(worker.DoWork), HttpContext.Current); 
} 
///………
class WorkerClass2 
{ 
    public void DoWork(object state) 
    { 
        HttpContext context = state as HttpContext; 
        Thread.Sleep(15000); 
        context.Response.Write("Request.Url = " + context.Request.Url); 
    } 
}

  

  然而,在我的環境中,上面的代碼會拋出一個異常,此異常來源於mscoree.dll類庫。上面的代碼與QueueUserWorkItem例子的缺陷:兩者都存在頁面請求的生命周期,同時分配HttpContext對象有效生命周期。實際的開發中我們可以保持一個HttpContext參考副本,以防止垃圾回收器將HttpContext回收,ASP.NET運行時是自由的,當頁面完成請求,將會自動清除一些垃圾資源。我不知道上面的代碼會發生什么,在不同的情況下,代碼可以在一些機器上運行,但失敗的幾率確實存在,並應避免這些導致失敗的條件。

  有一些方法可以保證頁面請求沒有完成,直到工作線程完成其工作,例如,下面的代碼。

 

private void Page_Load(object sender, System.EventArgs e)
{
   WorkerClass worker = new WorkerClass(_resetEvent);
   ThreadPool.QueueUserWorkItem(new WaitCallback(worker.DoWork),
                                HttpContext.Current);
   try
   {
      _resetEvent.WaitOne();
   }
   finally
   {
      _resetEvent.Close();
   }
}
AutoResetEvent _resetEvent = new AutoResetEvent(false);
…
class WorkerClass
{
   public WorkerClass(AutoResetEvent resetEvent)
   {  
      _resetEvent = resetEvent;
   }

   public void DoWork(object state)
   {
      try
      {
         HttpContext context = state as HttpContext;
         Thread.Sleep(500);
         context.Response.Write("Do work");
      }
      finally
      {
         _resetEvent.Set();
      }
   }

   AutoResetEvent _resetEvent = null;
}

  

  上面的代碼能正常的工作在瀏覽器中。 然而,設計仍然存在一些疑慮。 首先,ASP.NET運行時處理多個請求的時候,它的線程使用數量有限。 我們剛剛完成相同數量的工作,但我​​們已經增加了一倍所需的線程數,產生額外的上下文切換,和現在有一個同步原語來管理。 當等待工作任務完成,在原始線程執行額外的工作,則可能是一個好處。 然而,一般來說,你更應該使用額外的線程在ASP.NET中使用一定量的保留的做法。

 

  在這篇文章中,我們已經深入了解HttpContext.Current是如何工作的,並看到了一些場景,我們需要謹慎行事。 不要濫用HttpContext.Current,每當調用在代碼調用它的時候,應多檢查您的設計架構。

 

 

 


免責聲明!

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



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