AddOnPreRenderCompleteAsync ( new BeginEventHandler(MyBeginMethod), new EndEventHandler (MyEndMethod) );
public partial class AsyncPage : System.Web.UI.Page
{
private WebRequest _request;
void Page_Load (object sender, EventArgs e)
{
AddOnPreRenderCompleteAsync (
new BeginEventHandler(BeginAsyncOperation),
new EndEventHandler (EndAsyncOperation)
);
}
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 ();
}
}
图 2 同步和异步页处理
图 2 说明 ASP.NET 2.0 同步和异步页之间的区别。当请求同步页时,ASP.NET 为该请求分配线程池中的一个线程,并在该线程上执行页。如果该请求停止执行 I/O 操作,则挂起线程,直到完成操作,从而可以完成该页的生命周期。相反,异步页通常通过 PreRender 事件执行。然后,调用使用 AddOnPreRenderCompleteAsync 注册的 Begin 方法,之后,该请求处理线程返回线程池。Begin 启动一个异步 I/O 操作,当该操作完成时,ASP.NET 从线程池提取另一个线程并调用 End 方法,并且在该线程上执行该页生命周期的其余部分。
图 3 跟踪输出显示异步页的异步点
对 Begin 的调用标记该页的“异步点”。图 3 中的跟踪准确显示异步点发生在何处。如果调用,则必须在异步点之前调用 AddOnPreRenderCompleteAsync ― 即,不晚于该页的 PreRender 事件。
异步数据绑定
通常情况下,ASP.NET 页并不使用 HttpWebRequest 直接请求其他页,但它们通常查询数据库并对结果进行数据绑定。因此,您将如何使用异步页执行异步数据绑定呢?图 4 中的代码隐藏类显示进行此操作的一种方式。
public partial class AsyncDataBind : System.Web.UI.Page
{
private SqlConnection _connection;
private SqlCommand _command;
private SqlDataReader _reader;
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)
{
string connect = WebConfigurationManager.ConnectionStrings
["PubsConnectionString"].ConnectionString;
_connection = new SqlConnection(connect);
_connection.Open();
_command = new SqlCommand("SELECT title_id, title, price FROM titles", _connection);
return _command.BeginExecuteReader (cb, state);
}
void EndAsyncOperation(IAsyncResult ar)
{
_reader = _command.EndExecuteReader(ar);
}
protected void Page_PreRenderComplete(object sender, EventArgs e)
{
Output.DataSource = _reader;
Output.DataBind();
}
public override void Dispose()
{
if (_connection != null) _connection.Close();
base.Dispose();
}
}
AsyncDataBind.aspx.cs 与 AsyncPage.aspx.cs 使用相同的 AddOnPreRenderCompleteAsync 模式。但是,AsyncDataBind.aspx.cs 的 BeginAsyncOperation 方法调用 ADO.NET 2.0 中的新方法 SqlCommand.BeginExecuteReader(而非 HttpWebRequest.BeginGetResponse),以执行一个异步数据库查询。当调用完成时,EndAsyncOperation 调用 SqlCommand.EndExecuteReader 以获取 SqlDataReader,然后将其存储在私有字段中。在用于 PreRenderComplete 事件(在异步操作完成但呈现该页之前引发)的事件处理程序中,AsyncDataBind.aspx.cs 之后将 SqlDataReader 绑定到 Output GridView 控件。从外观上看,该页类似于使用 GridView 呈现数据库查询结果的普通(同步)页。但是在内部,该页更具可伸缩性,因为它并不挂起线程池线程以等待查询返回。
异步调用 Web 服务
另一个通常由 ASP.NET Web 页执行的、与 I/O 相关的任务是调出 Web 服务。由于 Web 服务调用花费较长时间才能返回,因此,执行它们的页是用于异步处理的理想选择。
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();
}
}
图 5 显示生成调出 Web 服务的异步页的方式。它使用图 1 和 图 4 中相同的 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; }
proxy.FooCompleted += new FooCompletedEventHandler (OnFooCompleted); proxy.FooAsync (...); ... void OnFooCompleted (Object source, FooCompletedEventArgs e) { // Called when Foo completes }
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 是从异步页进行多个异步 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。
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";
}
}