Cenni preliminari sul modello di progettazione asincrona

Una delle innovazioni introdotte dal modello asincrono consiste nel fatto che il chiamante può decidere se una particolare chiamata deve essere asincrona o meno. Per un oggetto chiamato non è necessaria un'ulteriore programmazione per il supporto del funzionamento asincrono da parte dei client in quanto i delegati asincroni eseguono questa operazione all'interno del modello. Common Language Runtime gestisce la differenza tra le visualizzazioni del chiamante e dell'oggetto chiamato. L'oggetto chiamato può scegliere di supportare in modo esplicito il funzionamento asincrono perché può implementarlo in modo più efficace rispetto a un'architettura generale oppure perché desidera tale supporto solo per i chiamanti. È tuttavia consigliabile che gli oggetti chiamati seguano il modello di progettazione asincrona per esporre le operazioni asincrone.

L'indipendenza dai tipi è un'ulteriore innovazione introdotta nel modello asincrono. In particolar modo per i delegati asincroni, un compilatore di linguaggio basato su .NET Framework e Common Language Runtime può generare firme del metodo indipendenti dai tipi per le operazioni di inizio e di fine che sono associate al metodo Invoke regolare, ad esempio per BeginInvoke ed EndInvoke. Questo è importante in quanto il compilatore suddivide la chiamata sincrona nelle operazioni di inizio e di fine per il delegato asincrono e consente in tal modo di passare solo parametri validi.

I concetti alla base di questo modello sono i seguenti:

  1. Il chiamante decide se una determinata chiamata deve essere asincrona.
  2. L'oggetto chiamato può scegliere se eseguire un'ulteriore programmazione per il supporto del comportamento asincrono da parte dei client, anche se ciò non è necessario. L'infrastruttura di Common Language Runtime deve essere in grado di gestire la differenza tra le visualizzazioni del chiamante e dell'oggetto chiamato.
  3. L'oggetto chiamato può scegliere di supportare in modo esplicito il funzionamento asincrono perché può implementarlo in modo più efficace rispetto a un'architettura generale oppure perché desidera tale supporto solo per i chiamanti. È tuttavia consigliabile che gli oggetti chiamati seguano il modello di progettazione asincrona per esporre le operazioni asincrone.
  4. Il compilatore genera per i delegati asincroni firme di metodo indipendenti dai tipi per BeginInvoke ed EndInvoke.
  5. .NET Framework fornisce i servizi necessari per il supporto di un modello di programmazione asincrona. Di seguito è riportato un esempio di elenco parziale di tali servizi:
    • Primitive di sincronizzazione, ad esempio monitor e blocchi di lettura e scrittura.
    • Thread e pool di thread.
    • Costrutti di sincronizzazione, ad esempio contenitori che supportano l'attesa di oggetti.
    • Esposizione a parti dell'infrastruttura sottostante, ad esempio oggetti IMessage e pool di thread.

Il modello suddivide una chiamata sincrona nelle parti che la costituiscono: un'operazione di inizio, un'operazione di fine e un oggetto risultante. Si consideri l'esempio che segue in cui per il completamento del metodo Factorize potrebbe essere richiesto molto tempo.

public class PrimeFactorizer
{
   public bool Factorize(int factorizableNum, ref int primefactor1, ref int primefactor2)
   {
      // Determine whether factorizableNum is prime.
      // If it is prime, return true. Otherwise, return false.
      // If it is prime, place factors in primefactor1 and primefactor2.
   }
}

Se si segue il modello asincrono, il writer della libreria di classi aggiunge i metodi BeginFactorize ed EndFactorize che suddividono l'operazione sincrona in due operazioni asincrone:

public class PrimeFactorizer
{
   public bool Factorize(
int factorizableNum, 
ref int primefactor1, 
ref int primefactor2)
   {
      // Determine whether factorizableNum is prime.
      // if it is prime, return true; otherwise return false.
      // if it is prime, place factors in primefactor1 and primefactor2
   }

   public IAsyncResult BeginFactorize(
int factorizableNum, 
ref int primefactor1, 
ref int primefactor2, 
AsyncCallback callback, 
Object state)
   {
     // Begin factoring asynchronously, and return a result object,
   }

   public bool EndFactorize(
ref int primefactor1, 
ref int primefactor2,
IAsyncResult asyncResult
)
   {
     // End (or complete) the factorizing, 
     // return the results, 
     // and obtain the prime factors.
   }
}

Il server suddivide l'operazione asincrona nelle sue due parti logiche: la parte che riceve l'input dal client e chiama l'operazione asincrona e la parte che fornisce al client i risultati dell'operazione.

Oltre all'input necessario per l'operazione asincrona, la prima parte riceve anche un delegato AsyncCallback da chiamare quando l'operazione asincrona è stata completata. La prima parte restituisce un oggetto waitable che implementa l'interfaccia IAsyncResult utilizzata dal client per determinare lo stato dell'operazione asincrona.

Il server utilizza inoltre l'oggetto waitable restituito al client per mantenere gli stati associati all'operazione asincrona. Il client utilizza la seconda parte per ottenere i risultati dell'operazione asincrona specificando l'oggetto waitable.

Di seguito sono elencate le opzioni a disposizione del client per l'inizializzazione delle operazioni asincrone:

  1. Fornire il delegato di callback quando viene avviata la chiamata asincrona.

    public class Driver1
    {
      public PrimeFactorizer primeFactorizer;
    
      public void Results(IAsyncResult asyncResult)
      {
        int primefactor1=0; 
        int primefactor2=0;
    
        bool prime = primeFactorizer.EndFactorize(
    ref primefactor1, 
    ref primefactor2,
    asyncResult);
      }
    
      public void Work()
      {
        int factorizableNum=1000589023, 
        int primefactor1=0; 
        int primefactor2=0;
        Object state = new Object();
    
        primeFactorizer = new PrimeFactorizer();
        AsyncCallback callback = new Callback(this.Results);
        IAsyncResult asyncResult = primeFactorizer.BeginFactorize(
    factorizableNum, 
    ref primefactor1, 
    ref primefactor2, 
    callback, 
    state);
      }
    }
    
  2. Non fornire il delegato di callback quando viene avviata l'operazione asincrona.

    public class Driver2
    {
      public static void Work()
      {
        int factorizableNum=1000589023, 
        int primefactor1=0; 
        int primefactor2=0;
        Object state = new Object();
    
        PrimeFactorizer primeFactorizer = new PrimeFactorizer();
        AsyncCallback callback = new Callback(this.Results);
        IAsyncResult asyncResult = primeFactorizer.BeginFactorize(
    factorizableNum, 
    ref primefactor1, 
    ref primefactor2, 
    callback, 
    state);
    
        bool prime = primeFactorizer.EndFactorize(
    ref primefactor1, 
    ref primefactor2,
    asyncResult);
      }
    }
    

Vedere anche

Firme del metodo asincrono | Interfaccia IAsyncResult | Delegato AsyncCallback per operazioni asincrone | Programmazione asincrona