ASP.NET-Anwendungen mit 'Wait'-Handles (ADO.NET)
Die Rückruf- und Abrufmodelle für die Behandlung asynchroner Vorgänge sind hilfreich, wenn die Anwendung nur eine asynchrone Operation gleichzeitig verarbeitet. Die Wait-Modelle ermöglichen eine flexiblere Verarbeitung mehrerer asynchroner Operationen. Es gibt zwei Wait-Modelle, benannt nach den zur Implementierung verwendeten WaitHandle-Methoden: das Wait (Any)-Modell und das Wait (All)-Modell.
Um ein Wait-Modell zu verwenden, müssen Sie die AsyncWaitHandle-Eigenschaft des IAsyncResult-Objekts verwenden, das von einer der Methoden BeginExecuteNonQuery, BeginExecuteReader oder BeginExecuteXmlReader zurückgegeben wurde. Für die WaitAny-Methode und die WaitAll-Methode ist es erforderlich, die WaitHandle-Objekte als Argument zu übergeben, das in einem Array gruppiert wird.
Beide Wait-Methoden überwachen die asynchronen Operationen und warten auf die Beendigung. Die WaitAny-Methode wartet auf die Beendigung oder das Timeout einer der Operationen. Wenn bekannt ist, dass eine bestimmte Operation abgeschlossen ist, können Sie die Ergebnisse verarbeiten und dann weiter auf die Beendigung oder das Timeout der nächsten Operation warten. Die WaitAll-Methode wartet vor dem Fortfahren auf die Beendigung oder das Timeout aller Prozesse im Array der WaitHandle-Instanzen.
Der Vorteil der Wait-Modelle tritt am deutlichsten zutage, wenn mehrere Operationen derselben Länge auf verschiedenen Servern ausgeführt werden müssen, oder wenn der Server leistungsfähig genug ist, um alle Abfragen gleichzeitig zu verarbeiten. In den hier vorgestellten Beispielen werden von drei Abfragen lange Prozesse emuliert, indem WAITFOR-Befehle unterschiedlicher Länge an bedeutungslose SELECT-Abfragen angefügt werden.
Beispiel: Wait (Any)-Modell
Im folgenden Beispiel wird das Wait (Any)-Modell veranschaulicht. Nach dem Starten von drei asynchronen Prozessen wird die WaitAny-Methode aufgerufen, die auf die Beendigung einer dieser Prozesse warten soll. Bei Beendigung jeweils eines Prozesses wird die EndExecuteReader-Methode aufgerufen, und das resultierende SqlDataReader-Objekt wird gelesen. An dieser Stelle füllt eine reale Anwendung einen Teil der Seite wahrscheinlich mit SqlDataReader auf. In diesem einfachen Beispiel wird einem Textfeld, das dem Prozess entspricht, der Zeitpunkt der Beendigung des Prozesses hinzugefügt. In den einzelnen Textfeldern wird somit Folgendes veranschaulicht: Bei jeder Beendigung eines Prozesses wird Code ausgeführt.
Um dieses Beispiel einzurichten, erstellen Sie ein neues ASP.NET-Websiteprojekt. Positionieren Sie ein Button-Steuerelement und vier TextBox-Steuerelemente auf der Seite (übernehmen Sie für jedes Steuerelement den Standardnamen).
Fügen Sie der Klasse des Formulars folgenden Code hinzu. Ändern Sie die Verbindungszeichenfolge entsprechend der Umgebung.
[Visual Basic]
' 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 "Data Source=(local);Integrated Security=SSPI;" & _
"Initial Catalog=AdventureWorks;" & _
"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
[C#]
// Add the following using statements, if they are not 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 "Data Source=(local);Integrated Security=SSPI;" +
"Initial Catalog=AdventureWorks;" +
"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();
}
Beispiel: Wait (All)-Modell
Im folgenden Beispiel wird das Wait (All)-Modell veranschaulicht. Nach dem Starten von drei asynchronen Prozessen wird die WaitAll-Methode aufgerufen, die auf die Beendigung oder das Timeout aller Prozesse warten soll.
Wie im Beispiel für das Wait (Any)-Modell wird einem Textfeld, das dem Prozess entspricht, der Zeitpunkt der Beendigung des Prozesses hinzugefügt. Anhand der Angaben in den Textfeldern wird Folgendes veranschaulicht: Code, der auf die WaitAny-Methode folgt, wird erst nach Beendigung aller Prozesse ausgeführt.
Um dieses Beispiel einzurichten, erstellen Sie ein neues ASP.NET-Websiteprojekt. Positionieren Sie ein Button-Steuerelement und vier TextBox-Steuerelemente auf der Seite (übernehmen Sie für jedes Steuerelement den Standardnamen).
Fügen Sie der Klasse des Formulars folgenden Code hinzu. Ändern Sie die Verbindungszeichenfolge entsprechend der Umgebung.
[Visual Basic]
' 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 "Data Source=(local);Integrated Security=SSPI;" & _
"Initial Catalog=AdventureWorks;" & _
"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
[C#]
// Add the following using statements, if they are not 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 "Data Source=(local);Integrated Security=SSPI;" +
"Initial Catalog=AdventureWorks;" +
"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();
}