使用等待句柄的 ASP.NET 应用程序
当应用程序一次只处理一个异步操作时,用于处理异步操作的回调和轮询模型将非常有用。 Wait 模型提供了一种更灵活的方法来处理多个异步操作。 有两种 Wait 模型,是根据用于实现它们的 WaitHandle 方法命名的:Wait (Any) 模型和 Wait (All) 模型。
若要使用任一 Wait 模型,需要使用 BeginExecuteNonQuery、BeginExecuteReader 或 BeginExecuteXmlReader 方法返回的 IAsyncResult 对象的 AsyncWaitHandle 属性。 WaitAny 和 WaitAll 方法都需要将 WaitHandle 对象作为参数发送,并分组到一个数组中。
两个 Wait 方法都监视异步操作,等待完成。 WaitAny 方法会等待任何操作完成或超时。一旦你知道特定操作已完成,就可以处理其结果,然后继续等待下一个操作完成或超时。WaitAll 方法会在继续之前等待一系列 WaitHandle 实例中的所有进程完成或超时。
当需要在不同的服务器上运行不同长度的多个操作时,或者当服务器的功能足以同时处理所有查询时,Wait 模型的好处最为明显。 在此处介绍的示例中,三个查询通过将不同长度的 WAITFOR 命令添加到无关紧要的 SELECT 查询来模拟长进程。
示例:等待(任何)模型
下例说明了 Wait (Any) 模型。 启动了三个异步进程后,将调用 WaitAny 方法来等待其中任何一个进程完成。 每个进程完成后,将调用 EndExecuteReader 方法,并读取生成的 SqlDataReader 对象。 此时,实际应用程序很可能会使用 SqlDataReader 来填充部分页面。 在这个简单的示例中,将进程完成的时间添加到与该进程相对应的文本框中。 合起来看,文本框中的这些时间说明以下结论:每次进程完成后都执行代码。
若要设置此示例,请创建新的 ASP.NET 网站项目。 将一个 Button 控件和四个 TextBox 控件置于该页(接受每个控件的默认名称)。
将以下代码添加到窗体的类中,根据环境的需要修改连接字符串。
' Add these to the top of the class
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Threading
' Add this code to the page's class:
Private Function GetConnectionString() As String
' To avoid storing the connection string in your code,
' you can retrieve it from a configuration file.
' If you have not included "Asynchronous Processing=true"
' in the connection string, the command will not be able
' to execute asynchronously.
Return "..." & _
"Asynchronous Processing=true"
End Function
Sub Button1_Click( _
ByVal sender As Object, ByVal e As System.EventArgs)
' In a real-world application, you might be connecting to
' three different servers or databases. For the example,
' we connect to only one.
Dim connection1 As New SqlConnection(GetConnectionString())
Dim connection2 As New SqlConnection(GetConnectionString())
Dim connection3 As New SqlConnection(GetConnectionString())
' To keep the example simple, all three asynchronous
' processes select a row from the same table. WAITFOR
' commands are used to emulate long-running processes
' that complete after different periods of time.
Dim commandText1 As String = _
"WAITFOR DELAY '0:0:01';" & _
"SELECT * FROM Production.Product " & _
"WHERE ProductNumber = 'BL-2036'"
Dim commandText2 As String = _
"WAITFOR DELAY '0:0:05';" & _
"SELECT * FROM Production.Product " & _
"WHERE ProductNumber = 'BL-2036'"
Dim commandText3 As String = _
"WAITFOR DELAY '0:0:10';" & _
"SELECT * FROM Production.Product " & _
"WHERE ProductNumber = 'BL-2036'"
Dim waitHandles(2) As WaitHandle
Try
' For each process, open a connection and begin execution.
' Use the IAsyncResult object returned by
' BeginExecuteReader to add a WaitHandle for the process
' to the array.
connection1.Open()
Dim command1 As New SqlCommand(commandText1, connection1)
Dim result1 As IAsyncResult = _
command1.BeginExecuteReader()
waitHandles(0) = result1.AsyncWaitHandle
connection2.Open()
Dim command2 As New SqlCommand(commandText2, connection2)
Dim result2 As IAsyncResult = _
command2.BeginExecuteReader()
waitHandles(1) = result2.AsyncWaitHandle
connection3.Open()
Dim command3 As New SqlCommand(commandText3, connection3)
Dim result3 As IAsyncResult = _
command3.BeginExecuteReader()
waitHandles(2) = result3.AsyncWaitHandle
Dim index As Integer
For countWaits As Integer = 1 To 3
' WaitAny waits for any of the processes to complete.
' The return value is either the index of the
' array element whose process just completed, or
' the WaitTimeout value.
index = WaitHandle.WaitAny(waitHandles, 60000, False)
' This example doesn't actually do anything with the
' data returned by the processes, but the code opens
' readers for each just to demonstrate the concept.
' Instead of using the returned data to fill the
' controls on the page, the example adds the time
' the process was completed to the corresponding
' text box.
Select Case index
Case 0
Dim reader1 As SqlDataReader
reader1 = command1.EndExecuteReader(result1)
If reader1.Read Then
TextBox1.Text = _
"Completed " & _
System.DateTime.Now.ToLongTimeString()
End If
reader1.Close()
Case 1
Dim reader2 As SqlDataReader
reader2 = command2.EndExecuteReader(result2)
If reader2.Read Then
TextBox2.Text = _
"Completed " & _
System.DateTime.Now.ToLongTimeString()
End If
reader2.Close()
Case 2
Dim reader3 As SqlDataReader
reader3 = command3.EndExecuteReader(result3)
If reader3.Read Then
TextBox3.Text = _
"Completed " & _
System.DateTime.Now.ToLongTimeString()
End If
reader3.Close()
Case WaitHandle.WaitTimeout
Throw New Exception("Timeout")
End Select
Next
Catch ex As Exception
TextBox4.Text = ex.ToString
End Try
connection1.Close()
connection2.Close()
connection3.Close()
End Sub
// Add the following using directives, if they aren't already there.
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Threading;
using System.Data.SqlClient;
// Add this code to the page's class
string GetConnectionString()
// To avoid storing the connection string in your code,
// you can retrieve it from a configuration file.
// If you have not included "Asynchronous Processing=true"
// in the connection string, the command will not be able
// to execute asynchronously.
{
return "/* Rest of connection string */" + "Asynchronous Processing=true";
}
void Button1_Click(object sender, System.EventArgs e)
{
// In a real-world application, you might be connecting to
// three different servers or databases. For the example,
// we connect to only one.
SqlConnection connection1 =
new SqlConnection(GetConnectionString());
SqlConnection connection2 =
new SqlConnection(GetConnectionString());
SqlConnection connection3 =
new SqlConnection(GetConnectionString());
// To keep the example simple, all three asynchronous
// processes select a row from the same table. WAITFOR
// commands are used to emulate long-running processes
// that complete after different periods of time.
string commandText1 = "WAITFOR DELAY '0:0:01';" +
"SELECT * FROM Production.Product " +
"WHERE ProductNumber = 'BL-2036'";
string commandText2 = "WAITFOR DELAY '0:0:05';" +
"SELECT * FROM Production.Product " +
"WHERE ProductNumber = 'BL-2036'";
string commandText3 = "WAITFOR DELAY '0:0:10';" +
"SELECT * FROM Production.Product " +
"WHERE ProductNumber = 'BL-2036'";
try
// For each process, open a connection and begin
// execution. Use the IAsyncResult object returned by
// BeginExecuteReader to add a WaitHandle for the
// process to the array.
{
connection1.Open();
SqlCommand command1 =
new SqlCommand(commandText1, connection1);
IAsyncResult result1 = command1.BeginExecuteReader();
WaitHandle waitHandle1 = result1.AsyncWaitHandle;
connection2.Open();
SqlCommand command2 =
new SqlCommand(commandText2, connection2);
IAsyncResult result2 = command2.BeginExecuteReader();
WaitHandle waitHandle2 = result2.AsyncWaitHandle;
connection3.Open();
SqlCommand command3 =
new SqlCommand(commandText3, connection3);
IAsyncResult result3 = command3.BeginExecuteReader();
WaitHandle waitHandle3 = result3.AsyncWaitHandle;
WaitHandle[] waitHandles = {
waitHandle1, waitHandle2, waitHandle3
};
int index;
for (int countWaits = 0; countWaits <= 2; countWaits++)
{
// WaitAny waits for any of the processes to
// complete. The return value is either the index
// of the array element whose process just
// completed, or the WaitTimeout value.
index = WaitHandle.WaitAny(waitHandles,
60000, false);
// This example doesn't actually do anything with
// the data returned by the processes, but the
// code opens readers for each just to demonstrate
// the concept.
// Instead of using the returned data to fill the
// controls on the page, the example adds the time
// the process was completed to the corresponding
// text box.
switch (index)
{
case 0:
SqlDataReader reader1;
reader1 =
command1.EndExecuteReader(result1);
if (reader1.Read())
{
TextBox1.Text =
"Completed " +
System.DateTime.Now.ToLongTimeString();
}
reader1.Close();
break;
case 1:
SqlDataReader reader2;
reader2 =
command2.EndExecuteReader(result2);
if (reader2.Read())
{
TextBox2.Text =
"Completed " +
System.DateTime.Now.ToLongTimeString();
}
reader2.Close();
break;
case 2:
SqlDataReader reader3;
reader3 =
command3.EndExecuteReader(result3);
if (reader3.Read())
{
TextBox3.Text =
"Completed " +
System.DateTime.Now.ToLongTimeString();
}
reader3.Close();
break;
case WaitHandle.WaitTimeout:
throw new Exception("Timeout");
break;
}
}
}
catch (Exception ex)
{
TextBox4.Text = ex.ToString();
}
connection1.Close();
connection2.Close();
connection3.Close();
}
示例:等待(所有)模型
下例说明了 Wait (All) 模型。 启动了三个异步进程后,将调用 WaitAll 方法以等待进程完成或超时。
与 Wait (Any) 模型的示例类似,进程完成的时间将添加到与该进程相对应的文本框中。 文本框中的这些时间再次说明以下结论:跟随在 WaitAny 方法后的代码只在所有进程都完成后才执行。
若要设置此示例,请创建新的 ASP.NET 网站项目。 将一个 Button 控件和四个 TextBox 控件置于该页(接受每个控件的默认名称)。
将以下代码添加到窗体的类中,根据环境的需要修改连接字符串。
' Add these to the top of the class
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Threading
' Add this code to the page's class:
Private Function GetConnectionString() As String
' To avoid storing the connection string in your code,
' you can retrieve it from a configuration file.
' If you have not included "Asynchronous Processing=true"
' in the connection string, the command will not be able
' to execute asynchronously.
Return "..." & _
"Asynchronous Processing=true"
End Function
Sub Button1_Click( _
ByVal sender As Object, ByVal e As System.EventArgs)
' In a real-world application, you might be connecting to
' three different servers or databases. For the example,
' we connect to only one.
Dim connection1 As New SqlConnection(GetConnectionString())
Dim connection2 As New SqlConnection(GetConnectionString())
Dim connection3 As New SqlConnection(GetConnectionString())
' To keep the example simple, all three asynchronous
' processes select a row from the same table. WAITFOR
' commands are used to emulate long-running processes
' that complete after different periods of time.
Dim commandText1 As String = _
"UPDATE Production.Product " & _
"SET ReorderPoint = ReorderPoint + 1 " & _
"WHERE ReorderPoint Is Not Null;" & _
"WAITFOR DELAY '0:0:01';" & _
"UPDATE Production.Product " & _
"SET ReorderPoint = ReorderPoint - 1 " & _
"WHERE ReorderPoint Is Not Null"
Dim commandText2 As String = _
"UPDATE Production.Product " & _
"SET ReorderPoint = ReorderPoint + 1 " & _
"WHERE ReorderPoint Is Not Null;" & _
"WAITFOR DELAY '0:0:05';" & _
"UPDATE Production.Product " & _
"SET ReorderPoint = ReorderPoint - 1 " & _
"WHERE ReorderPoint Is Not Null"
Dim commandText3 As String = _
"UPDATE Production.Product " & _
"SET ReorderPoint = ReorderPoint + 1 " & _
"WHERE ReorderPoint Is Not Null;" & _
"WAITFOR DELAY '0:0:10';" & _
"UPDATE Production.Product " & _
"SET ReorderPoint = ReorderPoint - 1 " & _
"WHERE ReorderPoint Is Not Null"
Dim waitHandles(2) As WaitHandle
Try
' For each process, open a connection and begin execution.
' Use the IAsyncResult object returned by
' BeginExecuteReader to add a WaitHandle for the process
' to the array.
connection1.Open()
Dim command1 As New SqlCommand(commandText1, connection1)
Dim result1 As IAsyncResult = _
command1.BeginExecuteNonQuery()
waitHandles(0) = result1.AsyncWaitHandle
connection2.Open()
Dim command2 As New SqlCommand(commandText2, connection2)
Dim result2 As IAsyncResult = _
command2.BeginExecuteNonQuery()
waitHandles(1) = result2.AsyncWaitHandle
connection3.Open()
Dim command3 As New SqlCommand(commandText3, connection3)
Dim result3 As IAsyncResult = _
command3.BeginExecuteNonQuery()
waitHandles(2) = result3.AsyncWaitHandle
' WaitAll waits for all of the processes to complete.
' The return value is True if all processes completed,
' False if any process timed out.
Dim result As Boolean = _
WaitHandle.WaitAll(waitHandles, 60000, False)
If result Then
Dim rowCount1 As Long = _
command1.EndExecuteNonQuery(result1)
TextBox1.Text = _
"Completed " & _
System.DateTime.Now.ToLongTimeString()
Dim rowCount2 As Long = _
command2.EndExecuteNonQuery(result2)
TextBox2.Text = _
"Completed " & _
System.DateTime.Now.ToLongTimeString()
Dim rowCount3 As Long = _
command3.EndExecuteNonQuery(result3)
TextBox3.Text = _
"Completed " & _
System.DateTime.Now.ToLongTimeString()
Else
Throw New Exception("Timeout")
End If
Catch ex As Exception
TextBox4.Text = ex.ToString
End Try
connection1.Close()
connection2.Close()
connection3.Close()
End Sub
// Add the following using directives, if they aren't already there.
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Threading;
using System.Data.SqlClient;
// Add this code to the page's class
string GetConnectionString()
// To avoid storing the connection string in your code,
// you can retrieve it from a configuration file.
// If you have not included "Asynchronous Processing=true"
// in the connection string, the command will not be able
// to execute asynchronously.
{
return "..." +
"Asynchronous Processing=true";
}
void Button1_Click(object sender, System.EventArgs e)
{
// In a real-world application, you might be connecting to
// three different servers or databases. For the example,
// we connect to only one.
SqlConnection connection1 =
new SqlConnection(GetConnectionString());
SqlConnection connection2 =
new SqlConnection(GetConnectionString());
SqlConnection connection3 =
new SqlConnection(GetConnectionString());
// To keep the example simple, all three asynchronous
// processes execute UPDATE queries that result in
// no change to the data. WAITFOR
// commands are used to emulate long-running processes
// that complete after different periods of time.
string commandText1 =
"UPDATE Production.Product " +
"SET ReorderPoint = ReorderPoint + 1 " +
"WHERE ReorderPoint Is Not Null;" +
"WAITFOR DELAY '0:0:01';" +
"UPDATE Production.Product " +
"SET ReorderPoint = ReorderPoint - 1 " +
"WHERE ReorderPoint Is Not Null";
string commandText2 =
"UPDATE Production.Product " +
"SET ReorderPoint = ReorderPoint + 1 " +
"WHERE ReorderPoint Is Not Null;" +
"WAITFOR DELAY '0:0:05';" +
"UPDATE Production.Product " +
"SET ReorderPoint = ReorderPoint - 1 " +
"WHERE ReorderPoint Is Not Null";
string commandText3 =
"UPDATE Production.Product " +
"SET ReorderPoint = ReorderPoint + 1 " +
"WHERE ReorderPoint Is Not Null;" +
"WAITFOR DELAY '0:0:10';" +
"UPDATE Production.Product " +
"SET ReorderPoint = ReorderPoint - 1 " +
"WHERE ReorderPoint Is Not Null";
try
// For each process, open a connection and begin
// execution. Use the IAsyncResult object returned by
// BeginExecuteReader to add a WaitHandle for the
// process to the array.
{
connection1.Open();
SqlCommand command1 =
new SqlCommand(commandText1, connection1);
IAsyncResult result1 = command1.BeginExecuteNonQuery();
WaitHandle waitHandle1 = result1.AsyncWaitHandle;
connection2.Open();
SqlCommand command2 =
new SqlCommand(commandText2, connection2);
IAsyncResult result2 = command2.BeginExecuteNonQuery();
WaitHandle waitHandle2 = result2.AsyncWaitHandle;
connection3.Open();
SqlCommand command3 =
new SqlCommand(commandText3, connection3);
IAsyncResult result3 = command3.BeginExecuteNonQuery();
WaitHandle waitHandle3 = result3.AsyncWaitHandle;
WaitHandle[] waitHandles = {
waitHandle1, waitHandle2, waitHandle3
};
bool result;
// WaitAll waits for all of the processes to
// complete. The return value is True if the processes
// all completed successfully, False if any process
// timed out.
result = WaitHandle.WaitAll(waitHandles, 60000, false);
if(result)
{
long rowCount1 =
command1.EndExecuteNonQuery(result1);
TextBox1.Text = "Completed " +
System.DateTime.Now.ToLongTimeString();
long rowCount2 =
command2.EndExecuteNonQuery(result2);
TextBox2.Text = "Completed " +
System.DateTime.Now.ToLongTimeString();
long rowCount3 =
command3.EndExecuteNonQuery(result3);
TextBox3.Text = "Completed " +
System.DateTime.Now.ToLongTimeString();
}
else
{
throw new Exception("Timeout");
}
}
catch (Exception ex)
{
TextBox4.Text = ex.ToString();
}
connection1.Close();
connection2.Close();
connection3.Close();
}