系列目錄
非同步處理請求的意義
大伙兒都知道,ASP.NET通過線程池處理請求,對於每個請求從線程池中請求一個可用的線程來處理請求,當請求處理完畢之後,線程資源將被歸還到線程池。然而,線程池中的線程是互斥資源,當網站在同一時刻的請求量達到一定數量的話,必然會導致這種資源不夠耗盡,新來的請求只能等待有新的線程歸還才能被處理。當然這不是最糟糕的,通常每個請求只需要很短的時間就可以了,新的請求不會等待太長的時間,但是,如果處理請求需要花費較長的時間呢?比如一次耗時的資料庫查詢、一次外部web service請求這類的IO操作。注意這裡特指的IO操作,指的是不會佔用ASP.NET線程池線程的,甚至不佔用本機CPU資源的操作。正因為如此,非同步處理請求在這種情況下尤其適用。當非同步處理請求時,佔用的線程會在耗時的IO操作開始前,將線程歸還給線程池,直到IO操作完成後,再從線程池中請求一個線程,並恢複當時的HttpContext,處理IO操作的結果。這樣就不會佔用寶貴的線程資源了。
MVC中的非同步Controller機制
MVC支援非同步地處理請求。可以通過下面的三種方式:
- 實現一個自訂的RouteHandler,並為GetHttpHandler方法返回一個實現IHttpAsynHandler的對象。
- 建立一個自訂的基類Controller,並實現IAsyncController,IAsyncController是IController的非同步版本。
- MVC內建了一個AsyncController,它實現了上述的IAsyncController,通過簡單的繼承AsyncController,即可實現非同步。
下面僅對第三種方法作簡單介紹。假設某個Action需要調用一個web service,並處理結果後返回:
public ContentResult GetPhotoByTag(string tag){...using (var response = WebRequest.Create(url).GetResponse()){// Parse the response as XMLvar xmlDoc = XDocument.Load(XmlReader.Create(response.GetResponseStream()));...}...}
顯然,如果這個web request消耗2s,那麼這個請求將hold這個線程至少2s。這種同步的處理方式顯然不合理,想要非同步處理,只需按如下步驟進行:
1、替換基類Controller為AsyncController
2、建立兩個配對的Action:ActionNameAsync和ActionNameCompleted。ActionNameAsync方法必須返回void,在內部啟動一個耗時的IO操作前,需要使用AsyncManager.OutstandingOperations.Increment()向MVC架構“註冊啟動”,在IO方法返回後,可以在AsyncManager.Parameters字典中儲存希望傳給ActionNameCompleted方法的參數。最後調用AsyncManager.OutstandingOperations.Decrement()通知MVC架構操作完成,此時,MVC架構會自動調用ActionNameCompleted。ActionNameCompleted需要向通常的Action一樣,返回一個ActionResult。因此上面的代碼需要改寫成如下這樣:
public void GetPhotoByTagAsync(string tag){//向MVC中註冊啟動AsyncManager.OutstandingOperations.Increment();...WebRequest request = WebRequest.Create(url);//啟動一個非同步web requestrequest.BeginGetResponse(asyncResult =>{using (WebResponse response = request.EndGetResponse(asyncResult)){var xml = XDocument.Load(XmlReader.Create(response.GetResponseStream()));...//將結果photoUrls,儲存在AsyncManager.Parameters中AsyncManager.Parameters["photoUrls"] = photoUrls;//通知MVC架構操作完成 ,準備調用CompletedAsyncManager.OutstandingOperations.Decrement();}}, null);}//像通常的Action一樣,這裡的參數photoUrls將在AsyncManager.Parameters中匹配public ContentResult GetPhotoByTagCompleted(IEnumerable<string> photoUrls){return Content(string.Format("<img src='{0}'/>", photoUrls.First()));}
當然,可以設定非同步作業的逾時時間:
[AsyncTimeout(10000)] // 10000 milliseconds equals 10 secondspublic void GetPhotoByTagAsync(string tag) { ... }
上面的代碼如果逾時了,將拋出TimeoutException異常,我們可以用希望的方式處理它。
當使用類似BeginGetResponse這類的非同步方法呼叫,並提供回呼函數參數時,你無法控制回呼函數調用在哪個線程上。大多數情況下,甚至不在ASP.NET的背景工作執行緒上。所以回呼函數無法關聯原始的HttpContext對象。
幸好,AsyncManager提供了一個Sync()方法,它會將一個委託在ASP.NET的背景工作執行緒上啟動,並關聯原始的HttpContext對象。而且它保證安全執行緒:
BeginAsyncOperation(asyncResult => {var result = EndAsyncOperation(asyncResult);// Can't always access System.Web.HttpContext.Current from here...Action doSomethingWithHttpContext = () => {// ... but can always access it from this delegate};if (asyncResult.CompletedSynchronously) // Already on an ASP.NET threaddoSomethingWithHttpContext();else // Must switch to an ASP.NET threadAsyncManager.Sync(doSomethingWithHttpContext);AsyncManager.OutstandingOperations.Decrement();}, null);
以上內容只是書中的內容摘錄。對非同步處理請求,我也沒有深入研究,等以後用到了再回過來研究吧。
ps:最近比較忙,沒有什麼時間關注在MVC上,見諒。
勞動果實,轉載請註明出處:http://www.cnblogs.com/P_Chou/archive/2011/01/07/details-asp-net-mvc-09.html