异步调用Web服务方法
基于Ajax技术构建的门户是web 2.0这一代中最为成功的Web应用程序。而这块市场上iGoogle和Pageflakes这两大站点已经走在了时代的前列。
当你打开Pageflakes,将会看到如下的界面:
接下来就是界面上的各个“部件”去向服务器请求各种web服务,而服务器作为代理,则代为向外部web服务发出请求。(这是因为ajax调用无法跨越,所以常通过代理来请求数据)
![](http://hi.csdn.net/attachment/201111/19/0_13217043728u1N.gif)
问题场景:某个很受用户欢迎的“部件”很长时间不能执行,导致很对请求无法及时执行,引起请求失败(超时),甚至超大的访问量会引起服务器死机,用户无法访问web站点。
解决方案:用户一进入他们访问的页面,各个“部件”会通过代理web服务发出一个调用请求,它从外部服务获取数据。外部服务要么执行地很快,要么会超时。因此,我们必须设置较长的超时时间,因为一般来说外部的很多大数据量的服务执行都很缓慢。从长期来看,因为asp.net ajax不支持异步web方法调用,为了解决这个问题,我们必须改变代理web服务到某个异步的HTTP处理器来实现。这就是下面将要介绍的——异步调用Web服务方法。
异步调用Web服务方法
默认情况下,在服务端Web服务上申明的所有Web方法都是同步执行的。然而,通过XML HTTP对象从浏览器端发出的调用是异步的,但是实际在服务器端执行Web方法的时候还是同步的。这意味着,从请求到来那一刻起要产生对Web方法调用的响应,它会从asp.net工作线程池中占用一个线程。如果从请求到调用完成占用相对较长的时间,那么请求的线程会一直进行处理直到调用的方法结束。不幸的是,大多数需要长时间来调用的是一些如长时间的数据库查询操作或者是对另一个Web服务的调用。例如,如果你需要对数据库进行调用,那么当前线程会一直等待直到调用完成。线程完全会什么事也不做一直处于等待状态直到探测到返回的查询结果。当某个线程在等待对TCP socket或后台某个web 服务调用完成的时候,会出现类似的问题。
当你通过使用Web方法来构造一个典型的asp.net Web服务应用的时候,编译器把代码编译为程序集以便当该请求的web方法被接收的时候进行调用。当你的应用程序首次启动的时候,ASMX处理器会通过反射程序集的方式来决定暴露哪个web方法。
对正常的同步请求,很简单的方式就是找出哪些方法和[WebMethod]特性有联系。
要使得Web方法是异步的,你需要确保满足如下规则:
有一个BeginXXX和EndXXX的Web方法,其中XXX是某个任意的字符串,它代表你想暴露的方法名称。
1、BeginXXX方法返回一个IasyncResult类型的接口并分别以AsyncCallback和某个对象作为最后的两个输入参数。
2、EndXXX方法带有一个IasyncResult接口作为其唯一的参数。
3、BeginXXX和EndXXX方法必须被标记为WebMethod特性。
如果ASMX处理器找到了满足上面所有条件的方法,然后它将像正常的Web方法一样暴露在WSDL文档上。
下面展示了一个同步方法的定义:
而下面是异步Web方法的定义:
ASMX处理器将从这对Web方法中暴露一个以Sleep命名的Web方法。该方法将接受之前在BeginXXX方法的签名中以AsyncCallback类型作为输入参数并在EndXXX方法中最后返回类型的参数类型。
ASMX处理器对经过编译的程序集进行反射处理并探测到某个异步的Web方法后,与处理同步请求相比,它必须处理这些不同方法的请求(BeginXXX、EndXXX)。不是同步调用Sleep方法并从返回值中产生响应,而是调用BeginSleep方法。它将发送来的请求反序列化成传递到函数中的参数,这个处理通常是针对同步请求,但它也像传递额外异步回调参数到BeginSleep方法那样向内部回调函数传递指针。【这有点像实例方法的调用,假如一个类有两个对象a,b,并且该类有一个实例方法run(),你也许会问,编译器是如何区分a.run()和b.run()?事实上编译器会把当前方法的调用者作为参数传递给调用方法】。
当ASMX处理器调用了BeginSleep方法后,它会返回线程到asp.net进程的线程池中去,因此它可以处理其他请求。然而,针对该请求的HttpContext则不会被释放。HttpContext将等待直到传递给完成处理请求的BeginSleep回调函数调用完成。
一旦回调函数被调用,就会从线程池中取出一个线程来执行剩余的工作。为了完成需要执行的多个处理请求并呈现数据作为响应返回,ASMX处理器将调用EndSleep方法,一旦该响应发送,HttpContext将获得释放。
当然还是有一些基本原理和局限性需要考虑:
(1) 当你使用业务逻辑层来对本身没有进行异步处理的数据执行读写操作,你不能使用异步Web方法。
(2) 当你同步调用外部web服务的时候,你不能使用异步方法。外部调用必须也是异步的才行。
(3) 当你使用常规的同步方法执行数据库操作的时候,你不能使用异步方法。只有所有数据库操作必须是异步的才行。
(4) 当执行一些不需要等待的I/O操作,如Http请求,Web服务调用,remoting,异步数据库调用或异步文件操作等,这对于采用异步Web方法调用没有什么好处。你不会从一个异步运行的方法执行简单的Delegate.BeginInvoke调用中获益,因为异步方法从相同的线程池如asp.net中的线程池指派线程。
以上“警告”可以总结为两点:
(1) 如果某个异步操作过程需要多次异步调用,那么每个步骤都要求是异步的,也就是要【一路异步到底】。——引用自博客园
(2) I/O密集型操作考虑异步,运算密集型操作考虑多线程(无等待的I/O操作不值得异步)。
因此,在上面示例代码中,简单的sleep方法和用于代理web服务的任何方法都不能成为真正的异步方法,我们需要重写它们来支持异步调用。在我们开始之前,请记住一个原则,当BeginXXX Web方法调用其他一些组件的BeginYYY方法结束并且你的EndXXX方法调用了该组件的EndYYY方法的时候,你仅仅只能从异步方法中获益。否则,异步使用Web方法没有任何好处。
下面展示了一个简单的股票报价代理Web服务实现代码。代理Web服务的BeginGetStock方法调用另外一个组件中的BeginGetStock方法,该方法从外部源抓取数据。当取得数据后,组件通过AsyncCallback cb对象执行回调。ASMX处理器程序将该回调向下传递给Web方法。因此,当它被调用的时候,asp.net的ASMX处理程序会收到该回调,并且会恢复HttpContext对象,调用EndGetStock方法和呈现该响应。
总结
上面,我已经展示了asp.net的ASMX处理程序提供了调用异步Web方法并返回线程到asp.net线程池的能力,而asp.netajax框架的asmx处理程序并没有这样的能力。它仅支持同步调用。因此,我们需要重写asp.net ajax框架的ASMX处理程序来支持异步Web方法的执行,并且当Web方法通过XML HTTP调用的时候绕开 asp.netajax的ASMX处理程序。接下来,我准备讲解asp.net ajax框架的ASMX是如何工作的,以及如何重写一个你自己的并且引入了很多新特性的处理程序。