當一個aspx頁面請求處理包括大量的IO工作,而這些IO資源又非常有限的情況下,那這個頁面在對面大量請求的時候就有可能導致大量線程等待處理,從而使應用程序線程開銷過多影響整體的處理效能.在這種情況我們更希望通過一個隊列的機制控制處理線程的開銷來實現更高效的處理效能.因此.net提供IHttpAsyncHandler來解決這些事情,但有個問題就是實現一個IHttpAsyncHandler意味着要自己要實現自己的處理過程,並不能對已經實現功能的.aspx進行控制.但通過反編譯.net代碼來看可以實現一個IHttpAsyncHandler接管現有的.aspx頁面實現異步處理,又不需要修改現有頁面實現的代碼.下面詳細講述實現過
從.net的web配置文件來看asp.net默認處理aspx的並不是IHttpHandler而是System.Web.UI.PageHandlerFactory,反編譯代碼看下
[PermissionSet(SecurityAction.InheritanceDemand, Unrestricted = true), PermissionSet(SecurityAction.LinkDemand, Unrestricted = true)] public class PageHandlerFactory : IHttpHandlerFactory2, IHttpHandlerFactory { private bool _isInheritedInstance; protected internal PageHandlerFactory() { this._isInheritedInstance = (base.GetType() != typeof(PageHandlerFactory)); } public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path) { return this.GetHandlerHelper(context, requestType, VirtualPath.CreateNonRelative(virtualPath), path); } IHttpHandler IHttpHandlerFactory2.GetHandler(HttpContext context, string requestType, VirtualPath virtualPath, string physicalPath) { if (this._isInheritedInstance) { return this.GetHandler(context, requestType, virtualPath.VirtualPathString, physicalPath); } return this.GetHandlerHelper(context, requestType, virtualPath, physicalPath); } public virtual void ReleaseHandler(IHttpHandler handler) { } private IHttpHandler GetHandlerHelper(HttpContext context, string requestType, VirtualPath virtualPath, string physicalPath) { Page page = BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page), context, true) as Page; if (page == null) { return null; } page.TemplateControlVirtualPath = virtualPath; return page; } }
從反編譯的代碼來看,看到的希望.首先PageHandlerFactory是可以繼承的,而GetHandler又是可重寫的,有了這兩個條件完全可以滿足我們的需要.通過承繼PageHandlerFactory就可以直接處理現有的aspx文件.
實現IHttpAsyncHandler
既然可以重寫PageHandlerFactory的GetHandler,而IhttpAsyncHandler又是繼承IHttpHandler;那事情就變得簡單多了可能通過構建一個IhttpAsyncHandler直接返回.
public class CustomPageFactory : System.Web.UI.PageHandlerFactory { static CustomPageFactory() { G_TaskQueue = new TaskQueue(20); } public static TaskQueue G_TaskQueue; public override IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path) { AspxAsyncHandler handler = new AspxAsyncHandler(base.GetHandler(context, requestType, virtualPath, path)); return handler; } }
可以實現一個IHttpAsyncHandler把PageHandlerFactory返回的IHttpHandler重新包裝一下
public class AspxAsyncHandler : IHttpAsyncHandler { public AspxAsyncHandler(IHttpHandler handler) { mHandler = handler; } private IHttpHandler mHandler; public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) { AspxAsyncResult result = new AspxAsyncResult(context, mHandler, cb); CustomPageFactory.G_TaskQueue.Add(result); return result; } public void EndProcessRequest(IAsyncResult result) { } public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { throw new NotImplementedException(); } }
這樣一個異步處理的httphandler就包裝完了.我們只需要通過配置httphandler就可以實現對現有的aspx進行異步隊列處理.
<handlers> <add name="custompage" verb="*" path="*.aspx" type="WebApp.Code.CustomPageFactory,WebApp"/> </handlers>
隊列和線程控制
在處理的過程中並沒有使用線程池來完成具體的工作,如果每個直接調用線程池那同樣面臨的問題就是線池線耗出現大量線程調度問題影響性能.所以在上面實現IHttpAsyncHandler的BeginProcessRequest方法中是構建一個IAsyncResult添加到隊列中.之於這個隊列的實現相對比較簡單:
public class TaskQueue { public TaskQueue(int group) { mDispatchs = new List<Dispatch>(group); for (int i = 0; i < group; i++) { mDispatchs.Add(new Dispatch()); } } private IList<Dispatch> mDispatchs; private long mIndex = 0; private int GetIndex() { return (int)System.Threading.Interlocked.Increment(ref mIndex) % mDispatchs.Count; } public void Add(AspxAsyncResult aspAsync) { if (aspAsync != null) { mDispatchs[GetIndex()].Push(aspAsync); } } class Dispatch { public Dispatch() { System.Threading.ThreadPool.QueueUserWorkItem(OnRun); } private Queue<AspxAsyncResult> mQueue = new Queue<AspxAsyncResult>(1024); public void Push(AspxAsyncResult aspAR) { lock (this) { mQueue.Enqueue(aspAR); } } private AspxAsyncResult Pop() { lock (this) { if (mQueue.Count > 0) return mQueue.Dequeue(); return null; } } private void OnRun(object state) { while (true) { AspxAsyncResult asyncResult = Pop(); if (asyncResult != null) { asyncResult.Execute(); } else { System.Threading.Thread.Sleep(10); } } } } }
為了更好地控制線程,隊列的實現是采用多隊列多線程機制,就是根據你需要的並發情況來指定線程隊列數來處理,當然這種設計是比較死板並不靈活,如果想設計靈活一點是根據當前隊列的處理情況和資源情況來動態計算擴沖現有隊列線程數.
IAsyncResult實現
異步返回處理對象實現也很簡單,實現一個Execute方法由隊列執行,執行完成后通過callBack方法來通知處理完成.
public class AspxAsyncResult : IAsyncResult { bool m_IsCompleted = false; private IHttpHandler mHandler; private HttpContext mContext; private AsyncCallback m_Callback; public AspxAsyncResult(HttpContext context, IHttpHandler handler, AsyncCallback cb) { mHandler = handler; mContext = context; m_Callback = cb; } #region IAsyncResult 成員 public object AsyncState { get { return null; } } public WaitHandle AsyncWaitHandle { get { return null; } } public bool CompletedSynchronously { get { return false; } } public bool IsCompleted { get { return m_IsCompleted; } } #endregion public void Execute() { try { mHandler.ProcessRequest(mContext); } catch { } finally { try { if (m_Callback != null) m_Callback(this); } catch { } m_IsCompleted = true; } } }
測試效果
為了驗證這種實現的有效性進行了一個簡單的測試,一個web頁面訪問一個邏輯服務,而交互過程連接池有線只有20個連接,如果當前連接池空了就必須等待其他連接回收.測試情況如下
- 沒用異步處理的情況:
- 使用了異步處理的情況(固定15線程處理):
從測試結果來看在沒異步處理的時候存在大量請求錯誤的同時,還存在大量的CPU資源損耗,而使用異步處理的測試結果整個處理過程中都保持平穩使有情況.當然同時要面對就是降低了一些處理量和在延時上高點,但這些都可以通過設置調度線程來達到一個更好的結果.
總結
從測試結果可以看到異步httphandler加隊列控制在某些場可以很好的控制線程的處理提高系統的穩定性和處理效能,更重要的一點是可以通過配置httphandler對現有的aspx進行異步請求處理,當然在配置的時候我們沒有必要針對所有aspx,只需要針對某些存IO操作而並發量又相對比較高的aspx即可.