ByVal cb As AsyncCallback, _
ByVal extraData As Object) As IAsyncResult
If Request.Url.AbsolutePath _
= "/WebApp/PreRequestHandlerPage.aspx" Then
Dim proxy As MyProxy = New MyProxy
proxy.Res = New MyAsyncResult
proxy.Res.result1
= proxy.BeginMethod1( _
500, _
New AsyncCallback(AddressOf MyCallback), _
proxy)
proxy.Res.result2
= proxy.BeginMethod1( _
300, _
New AsyncCallback(AddressOf MyCallback), _
proxy)
proxy.Res.Callback = cb
proxy.Res.State = extraData
proxy.Res.Proxy = proxy
Return proxy.Res
End If
Return New MyAsyncResult
End Function
关于此代码还有许多有趣的事情值得注意。首先,针对此虚拟目录处理的每个 HTTP 请求都将调用此代码。因此,我做的第一件事就是检查请求的实际路径,查看它是否是我要为其提供服务的页面的路径。
我的函数使用了一些有趣的输入参数来调用。cb 参数是 ASP.NET 传递给我的回调函数。ASP.NET 希望在我的异步工作完成后,可以调用由它提供给我的回调函数。它们就是通过这种方式知道何时调用我的 EndEventHandler。同样,如果我只进行一个 Web 服务调用,则只需将回调传递给 BeginMethod1 调用,然后 Web 服务调用将负责调用函数。但在本例中,我进行了两个单独的调用。因此,我创建了一个传递给两个 BeginMethod1 调用的中间回调函数,并且在回调代码中检查两个调用是否都已完成。如果没完成,我将返回;如果已完成,我将调用原始的回调。另一个有趣的参数是 extraData 参数,它在 ASP.NET 调用我时为 ASP.NET 保存了状态。我在调用由 cb 参数指定的回调函数时必须返回该状态信息,因此,我将其存储在所创建的 IAsyncResult 类中。我的回调代码如下所示:
Public Sub MyCallback(ByVal ar As IAsyncResult)
Dim proxy As MyProxy = ar.AsyncState
If proxy.Res.IsCompleted Then
proxy.Res.Callback.Invoke(proxy.Res)
End If
End Sub
还应当提到的一点是,我创建的实现 IAsyncResult 的类(称为 MyAsyncResult)将在查询 IsCompleted 属性时检查两个挂起 Web 服务调用的完成情况。
在 EndEventHandler 中,我只是从 Web 服务调用获取结果,然后将其存储在当前的请求上下文中。该上下文与要传递给 HttpHandler 的上下文相同。在本例中,它是 .aspx 请求的处理程序,这样它便可以用于我的标准代码。我的 EndEventHandler 代码如下所示:
Public Sub EndPreRequestHandlerExecute(ByVal ar As IAsyncResult)
If Request.Url.AbsolutePath _
= "/WebApp/PreRequestHandlerPage.aspx" Then
Dim res As MyAsyncResult = ar
Dim proxy As MyProxy = res.Proxy
Dim retString As String
retString = proxy.EndMethod1(proxy.Res.result1)
Context.Items.Add("WebServiceResult1", retString)
retString = proxy.EndMethod1(proxy.Res.result2)
Context.Items.Add("WebServiceResult2", retString)
End If
End Sub
由于已经接收了 .aspx 页面的数据,因此实际的页面处理也就非常简单了。
Public Class PreRequestHandlerPage
Inherits System.Web.UI.Page
Protected WithEvents Label1 As System.Web.UI.WebControls.Label
Protected WithEvents Label2 As System.Web.UI.WebControls.Label
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Label1.Text = Context.Items("WebServiceResult1")
Label2.Text = Context.Items("WebServiceResult2")
End Sub
End Class
这不仅仅是理论 -- 它确实起作用!
如果不考虑我没有阻塞了所有线程,至少也使得浪费的资源更少了,因而这还是有意义的。但实际的结果确实会有所不同吗?答案是肯定的“是”!我把此专栏中介绍的三种测试情况放在了一起:从 Web 页面代码进行 2 个阻塞的调用,从 Web 页面代码进行 2 个异步调用,以及从 PreRequestHandler 代码进行 2 个异步调用。我使用 Microsoft Application Center Test 对这三种情况进行了测试,在 60 秒钟内从 100 个虚拟客户端连续发送请求。下图显示的结果表明了在 60 秒钟内完成的请求数。
文章整理:西部数码--专业提供域名注册、虚拟主机服务
图 1:100 个同时进行请求的客户端在 60 秒钟内完成的请求
异步 PreRequestHandler 方法处理的请求数大约是排在第二位的方法处理的请求数的 8 倍。因此,该方法使您可以处理更多请求,但是对于单个请求,实际要多长时间才能完成呢?下图显示了这三种方法的平均响应时间。
图 2:100 个同时进行请求的客户端的平均完成响应时间
使用 PreRequestHandler 方法的平均请求响应时间仅为 3.2 秒。假设每个 Web 服务调用的内置延迟为 3 秒钟,则该方法是一种非常有效的解决办法。
我必须指出,这些并非科学的数字是在我的并非科学的办公室中运行的并非科学的计算机上获得的。当然,如果将空闲的线程释放出来,让它们做一些实际的工作确实会改善性能,因而这也很有意义。希望这些结果能够表明性能的改善其实是非常显著的。
PreRequestHandler 方法是很必要的,因为 .aspx 请求的处理程序中没有内置异步请求处理机制。但并非所有 ASP.NET HTTP 处理程序都是这样。PreRequestHandler 方法适用于所有 ASP.NET 请求类型,但使用将异步支持置于 .asmx 处理程序内的编程方式要比使用 PreRequestHandler 编程方式更容易一些。
小结
无论何时遇到任何类型的进程耗时较长的性能问题,异步执行模型都是一个很好的方法。在从 .aspx 页面调用 Web 服务的情况下,我们认为可以将异步 Web 服务调用与 ASP.NET 提供的异步执行模式结合起来。这解决了在处理 .aspx 请求的过程中缺乏异步支持的问题。使用此异步方法可以消除性能问题以及线程池资源的消耗问题。
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!




