实现对现有的aspx请求进行异步队列控制处理
当一个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即可.