下面展示使用 MethodAsync 模式异步调用 Web 服务的 GetTitles 方法的代码隐藏类。从功能上讲,该页等同于图 5 中的页。但其内部实现则大为不同。AsyncWSInvoke2.aspx 包括一个 @ Page Async=“true” 指令,类似于 AsyncWSInvoke1.aspx。但是,AsyncWSInvoke2.aspx.cs 并不调用 AddOnPreRenderCompleteAsync;它注册一个用于 GetTitlesCompleted 事件的处理程序,并调用 Web 服务代理上的 GetTitlesAsync。ASP.NET 仍然延迟呈现该页,直到 GetTitlesAsync 完成。在内部,当异步调用开始以及完成时,它使用 System.Threading.SynchronizationContext 的一个实例(2.0 的一个新类)接收通知。
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class AsyncWSInvoke2 : System.Web.UI.Page
{
private WS.PubsWebService _ws;
private DataSet _ds;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Hook PreRenderComplete event for data binding
this.PreRenderComplete += new EventHandler(Page_PreRenderComplete);
// Call the Web service asynchronously
_ws = new WS.PubsWebService();
_ws.GetTitlesCompleted += new
WS.GetTitlesCompletedEventHandler(GetTitlesCompleted);
_ws.Url = new Uri(Request.Url, "Pubs.asmx").ToString();
_ws.UseDefaultCredentials = true;
_ws.GetTitlesAsync();
}
}
void GetTitlesCompleted(Object source,
WS.GetTitlesCompletedEventArgs e)
{
_ds = e.Result;
}
protected void Page_PreRenderComplete(object sender, EventArgs e)
{
Output.DataSource = _ds;
Output.DataBind();
}
public override void Dispose()
{
if (_ws != null) _ws.Dispose();
base.Dispose();
}
}
使用 MethodAsync 而非 AddOnPreRenderCompleteAsync 实现异步页有两个优势。首先,MethodAsync 将模拟、区域性和 HttpContext.Current 注入 MethodCompleted 事件处理程序,而 AddOnPreRenderCompleteAsync 则不然。其次,如果该页进行多个异步调用,而且必须延迟呈现直到所有调用完成,则使用 AddOnPreRenderCompleteAsync 要求您生成一个在所有调用完成前保持无信号状态的 IasyncResult。使用 MethodAsync,这样的操作就不是必需的;您只需放置这些调用(数量不限),ASP.NET 引擎延迟该呈现阶段,直到最后一个调用返回。
异步任务
MethodAsync 是从异步页进行多个异步 Web 服务调用并延迟呈现阶段直到所有调用完成的一个简便方法。但如果您想在一个异步页中执行若干异步 I/O 操作,而且这些操作不涉及 Web 服务,那该如何呢? 这么说,可以反过来生成一个 IAsyncResult,它可以返回到 ASP.NET 以允许它了解最后一个调用何时完成的吗?幸运的是,答案是否定的。
在 ASP.NET 2.0 中,System.Web.UI.Page 类引入了另一个方法来简化异步操作: RegisterAsyncTask。RegisterAsyncTask 比 AddOnPreRenderCompleteAsync 具有四个优势。首先,除了 Begin 和 End 方法,RegisterAsyncTask 还允许您注册当异步操作长时间无法完成时调用的超时方法。您可以通过在该页的 @ Page 指令中包含 AsyncTimeout 属性以声明性方式设置超时。AsyncTimeout="5" 将超时设置为 5 秒。第二个优势是,您可以在一个请求中多次调用 RegisterAsyncTask 来注册若干异步操作。和使用 MethodAsync 一样,ASP.NET 延迟呈现该页,直到所有操作完成。第三,您可以使用 RegisterAsyncTask 的第四个参数将状态传递给 Begin 方法。最后,RegisterAsyncTask 将模拟、区域性和 HttpContext.Current 注入 End 和 Timeout 方法。正如本文前面提到的,使用 AddOnPreRenderCompleteAsync 注册的 End 方法的情况则不然。
在其他方面,依赖于 RegisterAsyncTask 的异步页与依赖于 AddOnPreRenderCompleteAsync 的异步页相类似。它仍然需要 @ Page 指令(或等效的编程指令,它会将该页的 AsyncMode 属性设置为 true)中的 Async=“true” 属性,而且它仍然与平时一样通过 PreRender 事件执行,此时调用使用 RegisterAsyncTask 注册的 Begin 方法,而且进一步保持请求处理直到最后一个操作完成。例如,图 7 中的代码隐藏类在功能上与图 1 中的等效,但是它使用 RegisterTaskAsync 而非使用 AddOnPreRenderCompleteAsync。请注意名为 TimeoutAsyncOperation 的超时处理程序,如果 HttpWebRequest.BeginGetRequest 长时间无法完成,将调用该处理程序。相应的 .aspx 文件包括一个将超时间隔设置为 5 秒的 AsyncTimeout 属性。还请注意传给 RegisterAsyncTask 的第四个参数(可用于将数据传送到 Begin 方法)的 null。