Modello di progettazione della programmazione asincrona

Nell'esempio di codice seguente viene mostrata una classe server che scompone un numero.

Public Class PrimeFactorizer   
   Public Function Factorize(factorizableNum As Long, ByRef primefactor1   
            As Long, ByRef primefactor2 As Long) As Boolean
      primefactor1 = 1
      primefactor2 = factorizableNum
      
      ' Factorize using a low-tech approach.
      Dim i As Integer
      For i = 2 To factorizableNum - 1
         If 0 = factorizableNum Mod i Then
            primefactor1 = i
            primefactor2 = factorizableNum / i
            Exit For
         End If
      Next i
      If 1 = primefactor1 Then
         Return False
      Else
         Return True
      End If
   End Function
End Class
[C#]
public class PrimeFactorizer
{
   public bool Factorize(long factorizableNum,  
            ref long primefactor1,
            ref long primefactor2)
   {
      primefactor1 = 1;
      primefactor2 = factorizableNum;

      // Factorize using a low-tech approach.
      for (int i=2;i<factorizableNum;i++)
      {
         if (0 == (factorizableNum % i))
         {
            primefactor1 = i;
            primefactor2 = factorizableNum / i;
            break;
         }
      }
      if (1 == primefactor1 )
         return false;
      else
         return true   ;
   }
}

Nell'esempio di codice seguente viene mostrato un client che definisce un modello per la chiamata asincrona del metodo Factorize dalla classe PrimeFactorizer nell'esempio precedente.

' Define the delegate.
Delegate Function FactorizingAsyncDelegate(factorizableNum As Long, ByRef    
      primefactor1 As Long, ByRef primefactor2 As Long)
End Sub
' Create an instance of the Factorizer.
Dim pf As New PrimeFactorizer()

' Create a delegate on the Factorize method on the Factorizer.
Dim fd As New FactorizingDelegate(pf.Factorize)
[C#]
// Define the delegate.
public delegate bool FactorizingAsyncDelegate(long factorizableNum,  
      ref long primefactor1,
      ref long primefactor2);

// Create an instance of the Factorizer.
PrimeFactorizer pf = new PrimeFactorizer();

// Create a delegate on the Factorize method on the Factorizer.
FactorizingDelegate fd = new FactorizingDelegate(pf.Factorize);

Dopo l'analisi della relativa definizione nella prima riga dell'esempio precedente, mediante il compilatore verrà creata la classe FactorizingAsyncDelegate riportata di seguito. Verranno generati i metodi BeginInvoke e EndInvoke.

Public Class FactorizingAsyncDelegate
   Inherits Delegate

   Public Function Invoke(factorizableNum As Long, ByRef primefactor1 As 
         Long, ByRef primefactor2 As Long) As Boolean
   End Function
   
   ' Supplied by the compiler.
   Public Function BeginInvoke(factorizableNum As Long, ByRef primefactor1 
         As Long, ByRef primefactor2 As Long, cb As AsyncCallback,
         AsyncState As Object) As       
         IasyncResult
   End Function
   
   ' Supplied by the compiler.
   Public Function EndInvoke(ByRef primefactor1 As Long, ByRef 
         primefactor2 As Long, ar As IAsyncResult) As Boolean
   End Function
[C#]
public class FactorizingAsyncDelegate : Delegate
{
   public bool Invoke(ulong factorizableNum,  
         ref ulong primefactor1, ref ulong primefactor2);

   // Supplied by the compiler.   
   public IAsyncResult BeginInvoke(ulong factorizableNum,  
            ref unsigned long primefactor1,
            ref unsigned long primefactor2, AsyncCallback cb, 
            Object AsyncState);

   // Supplied by the compiler.   
   public bool EndInvoke(ref ulong primefactor1,
               ref ulong primefactor2, IAsyncResult ar);
}

L'interfaccia utilizzata nel parametro del delegato nell'esempio di codice seguente viene definita nella libreria di classi .NET Framework. Per ulteriori informazioni, vedere Interfaccia IAsyncResult.

Delegate Function AsyncCallback(ar As IAsyncResult)

' Returns true if the asynchronous operation has been completed.         
Public Interface IasyncResult
      ' Handle to block on for the results.
      ReadOnly Property IsCompleted() As Boolean
         ' Get accessor implementation goes here.
      End Property 

      ' Caller can use this to wait until operation is complete.
      ReadOnly Property AsyncWaitHandle() As WaitHandle
         ' Get accessor implementation goes here.
      End Property 
      
      ' The delegate object for which the async call was invoked.
      ReadOnly Property AsyncObject() As [Object]
         ' Get accessor implementation goes here.
      End Property 
      
      ' The state object passed in through BeginInvoke.
      ReadOnly Property AsyncState() As [Object]
         ' Get accessor implementation goes here.
      End Property 
      
      ' Returns true if the call completed synchronously.
      ReadOnly Property CompletedSynchronously() As Boolean
         ' Get accessor implementation goes here.
      End Property
   End Interface
[C#]
public delegate AsyncCallback (IAsyncResult ar);

public interface IAsyncResult
{
   // Returns true if the asynchronous operation has completed.
   bool IsCompleted { get; }

   // Caller can use this to wait until operation is complete.
   WaitHandle AsyncWaitHandle { get; }

   // The delegate object for which the async call was invoked.
   Object AsyncObject { get; }

   // The state object passed in through BeginInvoke.
   Object AsyncState { get; }

   // Returns true if the call completed synchronously.
   bool CompletedSynchronously { get; }
}

Si noti che l'oggetto che implementa l'interfaccia IAsyncResult deve essere un oggetto waitable e la relativa primitiva di sincronizzazione sottostante deve essere segnalata dopo che la chiamata è stata annullata o completata. In questo modo il client attende il completamento della chiamata invece di eseguire il polling. Vengono forniti diversi oggetti waitable che rispecchiano le primitive di sincronizzazione Win32, ad esempio ManualResetEvent, AutoResetEvent e Mutex. Vengono inoltre forniti metodi che supportano l'attesa in modo che tali oggetti di sincronizzazione vengano segnalati con la semantica "any" o "all". Tali metodi sono sensibili al contesto per evitare blocchi critici.

Il metodo Cancel è una richiesta di annullamento dell'elaborazione del metodo dopo la scadenza del tempo di timeout desiderato. Si noti che si tratta solo di una richiesta da parte del client che il server dovrebbe eseguire. Inoltre il client non deve presumere che il server abbia interrotto completamente l'elaborazione della richiesta dopo avere ricevuto la notifica che il metodo è stato annullato. In altre parole, è consigliabile che il client non distrugga le risorse, ad esempio gli oggetti file, in quanto il server potrebbe utilizzarli attivamente. La proprietà IsCanceled verrà impostata su true se la chiamata era stata annullata e la proprietà IsCompleted verrà impostata su true dopo che il server ha completato l'elaborazione della chiamata. Dopo che il server imposta la proprietà IsCompleted su true, il server non può utilizzare le risorse fornite dal client all'esterno della semantica di condivisione concordata. Il client può distruggere le risorse dopo che la proprietà IsCompleted restituisce true.

La proprietà Server restituisce l'oggetto server che ha fornito IAsyncResult.

Nell'esempio di codice seguente viene mostrato il modello di programmazione sul lato client per richiamare il metodo Factorize in modalità asincrona.

public class ProcessFactorizeNumber
{
   private long _ulNumber;

   public ProcessFactorizeNumber(long number)
   {
      _ulNumber = number;
   }

   [OneWayAttribute()]
   public void FactorizedResults(IAsyncResult ar)
   {
      long factor1=0, factor2=0; 
      
      // Extract the delegate from the AsynchResult.
      FactorizingAsyncDelegate fd = 
         (FactorizingAsyncDelegate) ((AsyncResult)ar).AsyncDelegate;
      // Obtain the result.
      fd.EndInvoke(ref factor1, ref factor2, ar);

      // Output the results.
      Console.Writeline("On CallBack: Factors of {0} : {1} {2}",
                        _ulNumber, factor1, factor2);
   }
}

// Async Variation 1.
// The ProcessFactorizeNumber.FactorizedResults callback function
// is called when the call completes.
public void FactorizeNumber1()
{
   // Client code.
   PrimeFactorizer pf = new PrimeFactorizer();
   FactorizingAsyncDelegate fd = new FactorizingAsyncDelegate (pf.Factorize);

   long factorizableNum = 1000589023, temp=0; 

   // Create an instance of the class that
   // will be called when the call completes.
   ProcessFactorizedNumber fc = 
      new ProcessFactorizedNumber(factorizableNum);
   // Define the AsyncCallback delegate.   
   AsyncCallbackDelegate cb = new AsyncCallback(fc.FactorizedResults);
   // Any object can be the state object.
   Object state = new Object();

   // Asynchronously invoke the Factorize method on pf.
   // Note: If you have pure out parameters, you do not need the 
   // temp variable.
   IAsyncResult ar = fd.BeginInvoke(factorizableNum, ref temp, ref temp,  
                        cb, state); 

// Proceed to do other useful work.

// Async Variation 2.
// Waits for the result.
// Asynchronously invoke the Factorize method on pf.
// Note: If you have pure out parameters, you do not need 
// the temp variable.
public void FactorizeNumber2()
{
   // Client code.
   PrimeFactorizer pf = new PrimeFactorizer();
   FactorizingAsyncDelegate fd = new FactorizingAsyncDelegate (pf.Factorize);

   long factorizableNum = 1000589023, temp=0;
   // Create an instance of the class 
   // to be called when the call completes.
   ProcessFactorizedNumber fc = 
            new ProcessFactorizedNumber(factorizableNum);

   // Define the AsyncCallback delegate.
   AsyncCallback cb = new AsyncCallback(fc.FactorizedResults);

   // Any object can be the state object.
   Object state = new Object();

   // Asynchronously invoke the Factorize method on pf.
   IAsyncResult ar = fd.BeginInvoke(factorizableNum, ref temp, ref temp, 
                        null, null); 

   ar.AsyncWaitHandle.WaitOne(10000, false);

   if(ar.IsCompleted)
   {
      int factor1=0, factor2=0;

      // Obtain the result.
      fd.EndInvoke(ref factor1, ref factor2, ar);

      // Output the results.
      Console.Writeline("Sequential : Factors of {0} : {1} {2}", 
                     factorizableNum, factor1, factor2);
   }
}

Si noti che se FactorizeCallback è una classe associata a un contesto che richiede il contesto sincronizzato o di affinità dei thread, la funzione di callback viene inviata tramite l'infrastruttura del dispatcher del contesto. In altre parole, la funzione di callback stessa potrebbe essere eseguita in modalità asincrona rispetto ai chiamanti per tali contesti. Questa è la semantica del qualificatore a una via sulle firme del metodo. Qualsiasi chiamata al metodo potrebbe essere eseguita in modalità sincrona o asincrona rispetto al chiamante e il chiamante non può presumere il completamento di una chiamata di questo tipo quando il controllo di esecuzione ritorna.

Inoltre, la chiamata di EndInvoke prima del completamento dell'operazione asincrona bloccherà il chiamante. Non è definito il risultato ottenuto eseguendo la chiamata una seconda volta con lo stesso AsyncResult.

Riepilogo del modello di progettazione della programmazione asincrona

Il server divide un'operazione asincrona in due parti logiche: la parte che riceve l'input dal client e avvia l'operazione asincrona e la parte che fornisce i risultati dell'operazione asincrona al client. In aggiunta all'input necessario per il funzionamento asincrono, la prima parte richiama inoltre un oggetto AsyncCallbackDelegate al termine del funzionamento asincrono. La prima parte restituisce un oggetto waitable che implementa l'interfaccia IAsyncResult utilizzata dal client per determinare lo stato del funzionamento asincrono. In genere il server utilizza l'oggetto waitable restituito al client per mantenere lo stato associato al funzionamento asincrono. Il client utilizza la seconda parte per ottenere i risultati dell'operazione asincrona specificando l'oggetto waitable.

Durante l'inizializzazione delle operazioni asincrone il client può o meno fornire il delegato della funzione di callback.

Per il completamento delle operazioni asincrone il client può effettuare le operazioni riportate di seguito.

  • Esecuzione del polling dell'oggetto IAsyncResult restituito per il completamento.
  • Esecuzione di un blocco per tentare di completare l'operazione anticipatamente.
  • Attesa dell'oggetto IAsyncResult. La differenza tra questa opzione e la precedente è che il client può utilizzare i timeout per riprendere periodicamente il controllo.
  • Completamento dell'operazione all'interno della routine della funzione di callback.

Uno scenario in cui è possibile utilizzare entrambi i metodi di lettura e scrittura sincrona e asincrona è l'uso dell'input/output del file. Nell'esempio seguente viene illustrato il modello di progettazione mostrando come l'oggetto File implementa le operazioni di lettura e scrittura.

Public Class File   
   ' Other methods for this class go here.

   ' Synchronous read method.
   Function Read(buffer() As [Byte], NumToRead As Long) As Long 
   
   ' Asynchronous read method.
   Function BeginRead(buffer() As [Byte], NumToRead As Long, cb As   
            AsyncCallbackDelegate) As IAsyncResult   
   Function EndRead(ar As IAsyncResult) As Long 
   
   ' Synchronous write method.
   Function Write(buffer() As [Byte], NumToWrite As Long) As Long   
   
   ' Asynchrnous write method.
   Function BeginWrite(buffer() As [Byte], NumToWrite As Long, cb As   
            AsyncCallbackDelegate) As IAsyncResult 
   Function EndWrite(ar As IAsyncResult) As Long 
End Class
[C#]
public class File
{
   // Other methods for this class go here.

   // Synchronous read method.
   long Read(Byte[] buffer, long NumToRead);

   // Asynchronous read method.
   IAsyncResult BeginRead(Byte[] buffer, long NumToRead, 
            AsyncCallbackDelegate cb);
   long EndRead(IAsyncResult ar);

   // Synchronous write method.
   long Write(Byte[] buffer, long NumToWrite);

   // Asynchrnous write method.
   IAsyncResult BeginWrite(Byte[] buffer, long NumToWrite, 
            AsyncCallbackDelegate cb);

   long EndWrite(IAsyncResult ar);
}

Il client non può associare facilmente lo stato a una determinata operazione asincrona senza definire un nuovo delegato della funzione di callback per ciascuna operazione. È possibile risolvere questo problema facendo in modo che i metodi Begin, ad esempio BeginWrite, assumano un parametro dell'oggetto aggiuntivo che rappresenta lo stato e che viene acquisito in IAsyncResult.

Vedere anche

Istruzioni di progettazione per gli sviluppatori di librerie di classi | Esecuzione asincrona | Interfaccia IAsyncResult