• ASP.NET中的异步 HttpHandler实现
  • 浪子 发表于 2015/12/28 10:35:00 | 分类标签: HttpHandler 异步编程

  • 异步 HttpHandler

    关于HttpHandler的接口,我在【用Asp.net写自己的服务框架】中已有介绍, 这里就不再贴出它的接口代码了,只想说一句:那是个同步调用接口,它并没有异步功能。要想支持异步,则必须使用另一个接口:IHttpAsyncHandler
    // 摘要:
    //     定义 HTTP 异步处理程序对象必须实现的协定。
    public interface IHttpAsyncHandler : IHttpHandler
    {
        // 摘要:
        //     启动对 HTTP 处理程序的异步调用。
        // 参数:
        //   context:
        //     一个 System.Web.HttpContext 对象,该对象提供对用于向 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session
        //     和 Server)的引用。
        //   extraData:
        //     处理该请求所需的所有额外数据。
        //   cb:
        //     异步方法调用完成时要调用的 System.AsyncCallback。如果 cb 为 null,则不调用委托。
        // 返回结果:
        //     包含有关进程状态信息的 System.IAsyncResult。
        IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);
        // 摘要:
        //     进程结束时提供异步处理 End 方法。
        // 参数:
        //   result:
        //     包含有关进程状态信息的 System.IAsyncResult。
        void EndProcessRequest(IAsyncResult result);
    }
    这个接口也很简单,只有二个方法,并且与【C#客户端的异步操作】 提到的BeginXxxxx/EndXxxxx设计方式差不多。如果这样想,那么后面的事件就好理解了。
    在.net中,异步都是建立在IAsyncResult接口之上的,而BeginXxxxx/EndXxxxx是对这个接口最直接的使用方式。

    下面我们来看一下如何创建一个支持异步的ashx文件(注意:代码中的注释很重要)。
    public class AsyncHandler : IHttpAsyncHandler {
        private static readonly string ServiceUrl = "http://localhost:22132/service/DemoService/CheckUserLogin";    
        public void ProcessRequest(HttpContext context)
        {
            // 注意:这个方法没有必要实现。因为根本就不调用它。
            // 但要保留它,因为这个方法也是接口的一部分。
            throw new NotImplementedException();
        }    
        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            // 说明:
            //   参数cb是一个ASP.NET的内部委托,EndProcessRequest方法将在那个委托内部被调用。       
            LoginInfo info = new LoginInfo();
            info.Username = context.Request.Form["Username"];
            info.Password = context.Request.Form["Password"];
            MyHttpClient<LoginInfo, string> http = new MyHttpClient<LoginInfo, string>();
            http.UserData = context;
            // ================== 开始异步调用 ============================
            // 注意:您所需要的回调委托,ASP.NET已经为您准备好了,直接用cb就好了。
            return http.BeginSendHttpRequest(ServiceUrl, info, cb, http);
            // ==============================================================
        }
    
        public void EndProcessRequest(IAsyncResult ar)
        {
            MyHttpClient<LoginInfo, string> http = (MyHttpClient<LoginInfo, string>)ar.AsyncState;
            HttpContext context = (HttpContext)http.UserData;
            context.Response.ContentType = "text/plain";
            context.Response.Write("AsyncHandler Result: ");
            try {
                // ============== 结束异步调用,并取得结果 ==================
                string result = http.EndSendHttpRequest(ar);
                // ==============================================================
                context.Response.Write(result);
            }
            catch( System.Net.WebException wex ) {
                context.Response.StatusCode = 500;
                context.Response.Write(HttpWebRequestHelper.SimpleReadWebExceptionText(wex));
            }
            catch( Exception ex ) {
                context.Response.StatusCode = 500;
                context.Response.Write(ex.Message);
            }
        }
    实现其实是比较简单的,大致可以总结如下:
    1. 在BeginProcessRequest()方法,调用要你要调用的异步开始方法,通常会是另一个BeginXxxxx方法。
    2. 在EndProcessRequest()方法,调用要你要调用的异步结束方法,通常会是另一个EndXxxxx方法。
    真的就是这么简单。

    这里要说明一下,在【C#客户端的异步操作】中, 我演示了如何使用.net framework中的API去实现完整的异步发送HTTP请求的调用过程,但那个过程需要二次异步,而这个IHttpAsyncHandler接口却只支持一次回调。 因此,对于这种情况,就需要我们自己封装,将多次异步转变成一次异步。以下是我包装的一次异步的简化版本:

    下面这个包装类非常有用,我后面的示例还将会使用它。它也示范了如何创建自己的IAsyncResult封装。因此建议仔细阅读它。 (注意:代码中的注释很重要)
    /// <summary>
    /// 对异步发送HTTP请求全过程的包装类,
    /// 按IAsyncResult接口要求提供BeginSendHttpRequest/EndSendHttpRequest方法(一次回调)
    /// </summary>
    /// <typeparam name="TIn"></typeparam>
    /// <typeparam name="TOut"></typeparam>
    public class MyHttpClient<TIn, TOut>
    {
        /// <summary>
        /// 用于保存额外的用户数据。
        /// </summary>
        public object UserData;
        public IAsyncResult BeginSendHttpRequest(string url, TIn input, AsyncCallback cb, object state)
        {
            // 准备返回值
            MyHttpAsyncResult ar = new MyHttpAsyncResult(cb, state);
            // 开始异步调用
            HttpWebRequestHelper<TIn, TOut>.SendHttpRequestAsync(url, input, SendHttpRequestCallback, ar);
            return ar;
        }
        private void SendHttpRequestCallback(TIn input, TOut result, Exception ex, object state)
        {
            // 进入这个方法表示异步调用已完成
            MyHttpAsyncResult ar = (MyHttpAsyncResult)state;
            // 设置完成状态,并发出完成通知。
            ar.SetCompleted(ex, result);
        }    
        public TOut EndSendHttpRequest(IAsyncResult ar)
        {
            if( ar == null )
                throw new ArgumentNullException("ar");
            // 说明:我并没有检查ar对象是不是与之匹配的BeginSendHttpRequest实例方法返回的,
            // 虽然这是不规范的,但我还是希望示例代码能更简单。
            // 我想应该极少有人会乱传递这个参数。
            MyHttpAsyncResult myResult = ar as MyHttpAsyncResult;
            if( myResult == null )
                throw new ArgumentException("无效的IAsyncResult参数,类型不是MyHttpAsyncResult。");
            if( myResult.EndCalled )
                throw new InvalidOperationException("不能重复调用EndSendHttpRequest方法。");
            myResult.EndCalled = true;
            myResult.WaitForCompletion();            
            return (TOut)myResult.Result;
        }
    }
    internal class MyHttpAsyncResult : IAsyncResult
    {
        internal MyHttpAsyncResult(AsyncCallback callBack, object state)
        {
            _state = state;
            _asyncCallback = callBack;
        }
        internal object Result { get; private set; }
        internal bool EndCalled;
        private object _state;
        private volatile bool _isCompleted;
        private ManualResetEvent _event;
        private Exception _exception;
        private AsyncCallback _asyncCallback;
        public object AsyncState
        {
            get { return _state; }
        }
        public bool CompletedSynchronously
        {
            get { return false; } // 其实是不支持这个属性
        }
        public bool IsCompleted
        {
            get { return _isCompleted; }
        }
        public WaitHandle AsyncWaitHandle
        {
            get {
                if( _isCompleted )
                    return null;    // 注意这里并不返回WaitHandle对象。
                if( _event == null )     // 注意这里的延迟创建模式。
                    _event = new ManualResetEvent(false);
                return _event;
            }
        }
        internal void SetCompleted(Exception ex, object result)
        {
            this.Result = result;
            this._exception = ex;
            this._isCompleted = true;
            ManualResetEvent waitEvent = Interlocked.CompareExchange(ref _event, null, null);
            if( waitEvent != null )
                waitEvent.Set();        // 通知 EndSendHttpRequest() 的调用者
            if( _asyncCallback != null )
                _asyncCallback(this);    // 调用 BeginSendHttpRequest()指定的回调委托
        }
        internal void WaitForCompletion()
        {
            if( _isCompleted == false ) {
                WaitHandle waitEvent = this.AsyncWaitHandle;
                if( waitEvent != null )
                    waitEvent.WaitOne();    // 使用者直接(非回调方式)调用了EndSendHttpRequest()方法。
            }
    
            if( _exception != null )
                throw _exception;    // 将异步调用阶段捕获的异常重新抛出。
        }
    
        // 注意有二种线程竞争情况:
        //  1. 在回调线程中调用SetCompleted时,原线程访问AsyncWaitHandle
        //  2. 在回调线程中调用SetCompleted时,原线程调用WaitForCompletion
    
        // 说明:在回调线程中,会先调用SetCompleted,再调用WaitForCompletion
    }
    对于这个包装类来说,最关键还是MyHttpAsyncResult的实现,它是异步模式的核心。

  • 请您注意

    ·自觉遵守:爱国、守法、自律、真实、文明的原则

    ·尊重网上道德,遵守《全国人大常委会关于维护互联网安全的决定》及中华人民共和国其他各项有关法律法规

    ·严禁发表危害国家安全,破坏民族团结、国家宗教政策和社会稳定,含侮辱、诽谤、教唆、淫秽等内容的作品

    ·承担一切因您的行为而直接或间接导致的民事或刑事法律责任

    ·您在编程中国社区新闻评论发表的作品,本网站有权在网站内保留、转载、引用或者删除

    ·参与本评论即表明您已经阅读并接受上述条款

  • 感谢本文作者
  • 作者头像
  • 昵称:浪子
  • 加入时间:2013/6/13 0:00:00
  • TA的签名
  • 这家伙很懒,虾米都没写
  • +进入TA的空间
  • 以下内容也很赞哦
分享按钮