我所知道的HttpContext.Current


在MVC中,HttpContext.Current是比較常見的對象,可以用它來進行Session,Cache等的保存等。但是它並不是無處不在的,下面就慢慢來揭開它的面紗。

當我們向服務端發送請求的時候,頁面會響應我們的請求,比如我們訪問A頁面,那么服務端就會把A頁面通過處理后返回給我們,訪問B頁面,過程同樣。在這里,我們訪問A頁面和訪問B頁面,總共進行了2次請求,這兩次請求會不會是在同一個頁面線程中呢?因為Asp.net模型本身就是多線程模式的,那么讓我們來做一個實驗:

首先,創建一個Asp.net項目,新建一個Default.aspx主頁面,然后新建一個About.aspx從頁面,在Default.aspx頁面中,代碼如下:

   1:   protected void Page_Load(object sender, EventArgs e)
   2:   {
   3:        Response.Write("Default頁面線程ID:" + Thread.CurrentThread.ManagedThreadId);
   4:        HttpContext.Current.Cache.Insert("test", "TestValue", null, DateTime.Now.AddMinutes(120),System.Web.Caching.Cache.NoSlidingExpiration);
   5:   }

在代碼中,我們打印出了當前頁面的線程ID並做了一個簡單的緩存.

在About.aspx頁面中,代碼如下:

   1:   protected void Page_Load(object sender, EventArgs e)
   2:   {
   3:       bool flag = HttpContext.Current == null;
   4:       Response.Write("About頁面線程ID:" + Thread.CurrentThread.ManagedThreadId.ToString(+"<br/>");
   5:       Response.Write("About頁面的Httpcontext對象:"+(flag?"未創建":"已經創建")+"<br/>");
   6:       Response.Write("About頁面獲取的緩存值為:"+HttpContext.Current.Cache["test"]+"<br/>");
   7:  }

在代碼中,我們同樣打印出了當前頁面的線程ID並獲取之前的緩存:

得到的結果如下:

   1:  Default頁面線程ID:4
 
   3:  About頁面線程ID:8
   4:  About頁面的Httpcontext對象:已經創建
   5:  About頁面獲取的緩存值為:TestValue

從代碼輸出結果,我們可以看出,兩次請求分別被不同的線程處理,HttpContext.Current在這兩個線程中是可用的,為什么跨線程后還能夠使用呢?這個后面我們再來講解。

下面再看兩個例子,在APM模式處理中和子線程處理中,看看HttpContext.Current是否依然能夠訪問:

首先是APM模式的例子:

   1:  private void DO()
   2:  {
   3:        System.Threading.Thread.Sleep(1000);
   4:        Response.Write(HttpContext.Current==null?"沒有創建":"已經創建");
   5:  }
   6:   
   7:  private void BeginDO()
   8:  {
   9:        Action action = new Action(DO);
  10:        action.BeginInvoke((iar) =>
  11:        {
  12:            Action endAction = (Action)iar.AsyncState;
  13:             endAction.EndInvoke(iar);
  14:        }, action);
  15:  }

做好之后,讓我們運行起來,結果在執行

Response.Write(HttpContext.Current==null?"沒有創建":"已經創建");

這句話的時候報錯,提示:響應在此上下文中不可用。看來,在APM中,是不可用的。

那么在子線程中呢?

   1:  private void ThreadDo()
   2:  {
   3:       new Thread(new ThreadStart(DO)).Start();
   4:  }

讓我們執行,看看結果,不幸的是,當執行到同樣那句話的時候報錯,提示:響應在此上下文中不可用。

為什么在頁面中,跨線程可用,但是在APM模式或者是創建了子線程的場景中不能用呢?

就讓我們來剖析一下源代碼:

首先,HttpContext.Current源碼:

   1:  // System.Web.HttpContext
   2:  /// <summary>Gets or sets the <see cref="T:System.Web.HttpContext" /> object for the current HTTP request.</summary>
   3:  /// <returns>The <see cref="T:System.Web.HttpContext" /> for the current HTTP request.</returns>
   4:  public static HttpContext Current
   5:  {
   6:     get
   7:     {
   8:        return ContextBase.Current as HttpContext;
   9:     }
  10:     set
  11:     {
  12:        ContextBase.Current = value;
  13:     }
  14:  }

我們看到,是通過ContextBase.Current返回,讓我們繼續深究下去:

   1:  // System.Web.Hosting.ContextBase
   2:  internal static object Current
   3:  {    
   4:     get    
   5:     {        
   6:        return CallContext.HostContext;    
   7:     }    
   8:     [SecurityPermission(SecurityAction.Demand, Unrestricted = true)]    
   9:     set    
  10:     {        
  11:        CallContext.HostContext = value;    
  12:     }
  13:  }

走到這里我們看到,是通過CallContext.HostContext對象返回,繼續深究:

   1:  // System.Runtime.Remoting.Messaging.CallContext
   2:  /// <summary>Gets or sets the host context associated with the current thread.</summary>
   3:  /// <returns>The host context associated with the current thread.</returns>
   4:  /// <exception cref="T:System.Security.SecurityException">The immediate caller does not have infrastructure permission. </exception>
   5:  public static object HostContext
   6:  {
   7:      [SecurityCritical]
   8:      get
   9:      {
  10:          IllogicalCallContext illogicalCallContext = Thread.CurrentThread.GetIllogicalCallContext();
  11:          object hostContext = illogicalCallContext.HostContext;
  12:          if (hostContext == null)
  13:          {
  14:              LogicalCallContext logicalCallContext = CallContext.GetLogicalCallContext();
  15:              hostContext = logicalCallContext.HostContext;
  16:          }
  17:          return hostContext;
  18:      }
  19:      [SecurityCritical]
  20:      set
  21:      {
  22:          if (value is ILogicalThreadAffinative)
  23:          {
  24:              IllogicalCallContext illogicalCallContext = Thread.CurrentThread.GetIllogicalCallContext();
  25:              illogicalCallContext.HostContext = null;
  26:              LogicalCallContext logicalCallContext = CallContext.GetLogicalCallContext();
  27:              logicalCallContext.HostContext = value;
  28:              return;
  29:          }
  30:          LogicalCallContext logicalCallContext2 = CallContext.GetLogicalCallContext();
  31:          logicalCallContext2.HostContext = null;
  32:          IllogicalCallContext illogicalCallContext2 = Thread.CurrentThread.GetIllogicalCallContext();
  33:          illogicalCallContext2.HostContext = value;
  34:      }
  35:  }

看到了什么?從字面意思,我們可以看到:如果當前線程中存在與之相關的對象,則直接返回,否則就創建一個關聯的對象並返回。

那么,也就是說,雖然請求的頁面的線程變了,但是由於HttpContext.Current被當做相關對象被創建,所以可以在不同的頁面中獲取其實例。

但是為什么在APM模式或者子線程場景中不能訪問呢?由於APM模式或者子線程場景的處理是被操作系統所接管,因為apm模式需要操作系統分配線程來進行異步處理,子線程也是由操作系統調配,這些與當前的請求已經沒有關系,所以會出現無響應的情況。下面一幅圖代表我對這個的理解:

另外,引用一下Fish-li的文章,Httpcontext.current在如下場景中是不可用的,我們看到,這些場景操作都是被操作系統所接管:

1. 定時器的回調。
2. Cache的移除通知。
3. APM模式下異步完成回調。
4. 主動創建線程或者將任務交給線程池來執行。

最后,有人說,那我可以把HttpContext.current對象全局緩存,然后放到子線程中使用可以么?我只能說,仍然不可以,仍然會出現“響應在此上下文中不可用”的錯誤提示。你不信可以試試看。

由於HttpContext.Current對象會被關聯創建,所以,全局緩存他是沒必要的。


免責聲明!

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



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