技术开发 频道

ASP.NET中的异步页功能应用(下)

【IT168技术文档】

异步调用 Web 服务

  另一个通常由 ASP.NET Web 页执行的、与 I/O 相关的任务是调出 Web 服务。由于 Web 服务调用花费较长时间才能返回,因此,执行它们的页是用于异步处理的理想选择。

using System; using System.Data; using System.Configuration; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; public partial class AsyncWSInvoke1 : 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);    // Register async methods    AddOnPreRenderCompleteAsync(new BeginEventHandler (BeginAsyncOperation),new EndEventHandler(EndAsyncOperation)    );   }  }  IAsyncResult BeginAsyncOperation (object sender, EventArgs e, AsyncCallback cb, object state)  {   _ws = new WS.PubsWebService();   // Fix up URL for call to local VWD-hosted Web service   _ws.Url = new Uri(Request.Url, "Pubs.asmx").ToString();   _ws.UseDefaultCredentials = true;   return _ws.BeginGetTitles (cb, state);  }  void EndAsyncOperation(IAsyncResult ar)  {   _ds = _ws.EndGetTitles(ar);  }  protected void Page_PreRenderComplete(object sender, EventArgs e)  {   Output.DataSource = _ds;   Output.DataBind();  }  public override void Dispose()  {   if (_ws != null) _ws.Dispose();    base.Dispose();  } }
  上面显示生成调出 Web 服务的异步页的方式。它使用AddOnPreRenderCompleteAsync 机制。该页的 Begin 方法通过调用 Web 服务代理的异步 Begin 方法启动一个异步 Web 服务调用。该页的 End 方法在私有字段中缓存对 Web 方法返回的 DataSet 的引用,并且 PreRenderComplete 处理程序将 DataSet 绑定到 GridView。作为参考,该调用的目标 Web 方法如以下代码所示:
[WebMethod] public DataSet GetTitles () { string connect = WebConfigurationManager.ConnectionStrings ["PubsConnectionString"].ConnectionString; SqlDataAdapter adapter = new SqlDataAdapter ("SELECT title_id, title, price FROM titles", connect); DataSet ds = new DataSet(); adapter.Fill(ds); return ds; }
  这只是其中一种方式,但并不是唯一的方式。.NET Framework 2.0 Web 服务代理支持两种对 Web 服务进行异步调用的机制。一个是 .NET Framework 1.x 和 2.0 Web 服务代理中的每方法 Begin 和 End 方法。另一个是仅由 .NET Framework 2.0 的 Web 服务代理提供的新 MethodAsync 方法和 MethodCompleted 事件。
 
  如果一个 Web 服务有一个名为 Foo 的方法,那么除了具有名为 Foo、BeginFoo 和 EndFoo 的方法外,.NET Framework 版本 2.0 Web 服务代理还包括名为 FooAsync 的方法和名为 FooCompleted 的事件。可以通过注册 FooCompleted 事件的处理程序并调用 FooAsync 来异步调用 Foo,如下所示:
proxy.FooCompleted += new FooCompletedEventHandler (OnFooCompleted); proxy.FooAsync (...); ... void OnFooCompleted (Object source, FooCompletedEventArgs e) { // Called when Foo completes }
  当异步调用由于 FooAsync 完成而开始时,将引发 FooCompleted 事件,从而导致调用 FooCompleted 事件处理程序。包装该事件处理程序 (FooCompletedEventHandler) 的委托和传递给它的第二个参数 (FooCompletedEventArgs) 都随 Web 服务代理一起生成。可通过 FooCompletedEventArgs.Result 访问 Foo 的返回值。

  下面展示使用 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。


using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Net; using System.IO; using System.Text; using System.Text.RegularExpressions; public partial class AsyncPageTask : System.Web.UI.Page {  private WebRequest _request;  protected void Page_Load(object sender, EventArgs e)  {   PageAsyncTask task = new PageAsyncTask(    new BeginEventHandler(BeginAsyncOperation),    new EndEventHandler(EndAsyncOperation),    new EndEventHandler(TimeoutAsyncOperation),   null  );  RegisterAsyncTask(task);  }  IAsyncResult BeginAsyncOperation(object sender, EventArgs e, AsyncCallback cb, object state)  {   _request = WebRequest.Create("http://msdn.microsoft.com");   return _request.BeginGetResponse(cb, state);  }  void EndAsyncOperation(IAsyncResult ar)  {   string text;   using (WebResponse response = _request.EndGetResponse(ar))   {    using (StreamReader reader = new StreamReader (response.GetResponseStream()))     {      text = reader.ReadToEnd();     }   }   Regex regex = new Regex("href\\s*=\\s*\"([^\"]*)\"", RegexOptions.IgnoreCase);   MatchCollection matches = regex.Matches(text);   StringBuilder builder = new StringBuilder(1024);   foreach (Match match in matches)   {    builder.Append(match.Groups[1]);    builder.Append("<br/>");   }   Output.Text = builder.ToString();  }  void TimeoutAsyncOperation(IAsyncResult ar)  {   Output.Text = "Data temporarily unavailable";  } }
  RegisterAsyncTask 的主要优势在于,它允许异步页引发多个异步调用,并延迟呈现直到所有调用完成。它也很好地适用于单个异步调用,而且它提供了 AddOnPreRenderCompleteAsync 不具有的超时选项。如果生成一个只进行一个异步调用的异步页,您可以使用 AddOnPreRenderCompleteAsync 或 RegisterAsyncTask。但对于放置两个以上异步调用的异步页,RegisterAsyncTask 极大地简化了您的操作。
 
  由于超时值是每页而非每调用设置,因此您可能想知道是否能改变单个调用的超时值。简单的回答是否。您可以通过以编程方式修改页的 AsyncTimeout 属性,逐个请求地更改超时,但是您无法将不同超时分配给从同一请求初始化的不同调用。
包装它
 
  现在,您已经了解了 ASP.NET 2.0 中异步页的实质。它们在即将推出的 ASP.NET 版本中非常易于实现,并且其体系结构允许您在一个请求中批处理多个异步 I/O 操作,并延迟该页的呈现直到所有操作完成。通过与异步 ADO.NET 和 .NET Framework 中的其他新异步功能相结合,异步 ASP.NET 页针对因充满线程池而限制可伸缩性的 I/O 绑定请求问题提供了解决方案。
 
  当生成异步页时最后需要注意的一点是,不应该启动来自 ASP.NET 使用的同一线程池的异步操作。例如,在页的异步点调用 ThreadPool.QueueUserWorkItem 会起反作用,因为该方法来自线程池,从而导致纯粹获取用于处理请求的零线程。相反,调用内置于 Framework 中的异步方法(例如,方法 HttpWebRequest.BeginGetResponse 和 SqlCommand.BeginExecuteReader)通常认为是安全的,因为这些方法倾向于使用完成端口实现异步行为。
0
相关文章