Lingua

Applicazioni ASP.NET che utilizzano handle di attesa

Installare il pacchetto NuGet Microsoft.Data.SqlClient per usare questi esempi. Microsoft.Data.SqlClient supporta i metodi BeginExecute e EndExecute illustrati qui senza un'opzione asincrona della stringa di connessione. Non aggiungere Asynchronous Processing=true; Microsoft.Data.SqlClient non supporta tale parola chiave. Per il nuovo codice, preferire ExecuteReaderAsync e ExecuteNonQueryAsync con awaite usare Task.WhenAny o Task.WhenAll per coordinare le operazioni senza bloccare un thread di richiesta.

I modelli di callback e di polling per la gestione delle operazioni asincrone sono utili quando l'applicazione elabora solo un'operazione asincrona alla volta. I modelli di attesa offrono un metodo più flessibile per l'elaborazione di più operazioni asincrone. Sono disponibili due modelli di attesa, denominati in base ai metodi WaitHandle usati per implementarli: il modello Wait (Any) e il modello Wait (All).

Per usare uno dei due modelli Wait, utilizzare la proprietà AsyncWaitHandle dell'oggetto IAsyncResult restituito dai metodi BeginExecuteNonQuery, BeginExecuteReader o BeginExecuteXmlReader. Passare un array di oggetti WaitHandle a WaitAny o WaitAll.

Entrambi i metodi di attesa monitorano le operazioni asincrone, in attesa del completamento. Il metodo WaitAny attende il completamento o il timeout di una delle operazioni. Una volta completata un'operazione specifica, è possibile elaborarne i risultati e quindi continuare ad attendere il completamento o il timeout dell'operazione successiva. Il metodo WaitAll attende il completamento o il timeout di tutti i processi nella matrice di istanze WaitHandle prima di continuare.

Il vantaggio dei modelli di attesa è più evidente quando è necessario eseguire più operazioni di una certa lunghezza su server diversi o quando il server è abbastanza potente da elaborare tutte le query nello stesso momento. Negli esempi presentati qui, tre query emulano processi lunghi aggiungendo comandi WAITFOR di lunghezza variabile a query irrilevanti SELECT.

Esempio: Modello Wait (Any)

Nell'esempio seguente è illustrato il modello Wait (Any). Dopo l'avvio di tre operazioni asincrone, il codice chiama WaitAny per attendere il completamento di qualsiasi operazione. Per ogni operazione completata, il codice chiama EndExecuteReader e legge il SqlDataReader risultante. Un'applicazione può usare tali risultati per popolare parte della pagina. L'esempio aggiunge invece il tempo di completamento alla casella di testo corrispondente. Ogni record temporale indica quando il codice elabora un'operazione completata.

Poiché gli handle completati rimangono segnalato, l'esempio rimuove ogni operazione completata dagli elenchi in sospeso prima di attendere di nuovo.

Per impostare questo esempio, creare un nuovo progetto di sito Web ASP.NET. Inserire un controllo Button e quattro controlli TextBox nella pagina, accettando il nome predefinito per ogni controllo.

Aggiungere il codice seguente alla classe del form. In entrambi gli esempi di modello Wait sostituire i puntini di sospensione restituiti da GetConnectionString con la stringa di connessione. Per un server remoto con autenticazione integrata di Windows, usare Server=<server>;Database=<database>;Integrated Security=true;Encrypt=true. Sostituire <server> e <database> con i nomi del server e del database e configurare un certificato server attendibile dal client. Per altre opzioni di autenticazione, vedere Sintassi della stringa di connessione.

' Add these to the top of the class
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports Microsoft.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.

        Return "..."
    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 As New List(Of WaitHandle)
        Dim pending As New List(Of Integer) From {0, 1, 2}
        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 pending list.
            connection1.Open()
            Dim command1 As New SqlCommand(commandText1, connection1)
            Dim result1 As IAsyncResult = _
             command1.BeginExecuteReader()
            waitHandles.Add(result1.AsyncWaitHandle)

            connection2.Open()
            Dim command2 As New SqlCommand(commandText2, connection2)
            Dim result2 As IAsyncResult = _
             command2.BeginExecuteReader()
            waitHandles.Add(result2.AsyncWaitHandle)

            connection3.Open()
            Dim command3 As New SqlCommand(commandText3, connection3)
            Dim result3 As IAsyncResult = _
             command3.BeginExecuteReader()
            waitHandles.Add(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.ToArray(), 60000, False)
                If index = WaitHandle.WaitTimeout Then
                    Throw New Exception("Timeout")
                End If
                ' 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 pending(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()
                End Select
                waitHandles.RemoveAt(index)
                pending.RemoveAt(index)
            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.Collections.Generic;
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 Microsoft.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.
{
     return "...";
}
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 pending list.
     {
          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;

          List<WaitHandle> waitHandles = new List<WaitHandle> {
               waitHandle1, waitHandle2, waitHandle3
          };
          List<int> pending = new List<int> { 0, 1, 2 };

          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.ToArray(),
                    60000, false);
               if (index == WaitHandle.WaitTimeout)
               {
                    throw new Exception("Timeout");
               }
               //  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 (pending[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;
               }
               waitHandles.RemoveAt(index);
               pending.RemoveAt(index);
          }
     }
     catch (Exception ex)
     {
          TextBox4.Text = ex.ToString();
     }
     connection1.Close();
     connection2.Close();
     connection3.Close();
}

Esempio: Modello Wait (All)

Nell'esempio seguente è illustrato il modello Wait (All). Dopo l'avvio di tre processi asincroni, viene chiamato il metodo WaitAll per attendere il completamento o il timeout dei processi.

Come nell'esempio del modello Wait (Any), l'ora in cui il processo è stato completato viene aggiunta a una casella di testo corrispondente al processo. Nuovamente, le ore nelle caselle di testo illustrano il punto: il codice che segue il metodo WaitAny viene eseguito solo dopo il completamento di tutti i processi.

Per impostare questo esempio, creare un nuovo progetto di sito Web ASP.NET. Inserire un controllo Button e quattro controlli TextBox nella pagina, accettando il nome predefinito per ogni controllo.

Aggiungere il codice seguente alla classe del modulo, modificando la stringa di connessione in base alle esigenze dell'ambiente.

' Add these to the top of the class
Imports System
Imports System.Data
Imports Microsoft.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.

        Return "..."
    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 Microsoft.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.
{
    return "...";
}
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();
}

Vedi anche