在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對象會被關聯創建,所以,全局緩存他是沒必要的。