Cenni preliminari sul modello asincrono basato su eventi

Le applicazioni che eseguono più attività contemporaneamente, pur rimanendo disponibili all'utente, richiedono spesso una progettazione che preveda l'uso di più thread. Lo spazio dei nomi System.Threading offre tutti gli strumenti necessari per creare applicazioni multithreading a elevate prestazioni, per l'uso dei quali è necessaria tuttavia una notevole esperienza nel campo dell'ingegneria del software multithreading. Per applicazioni multithreading relativamente semplici, il componente BackgroundWorker rappresenta una soluzione adeguata. Per applicazioni asincrone più complesse, si consiglia di implementare una classe che segua il modello asincrono basato su eventi.

Tale modello consente di usufruire dei vantaggi offerti dalle applicazioni multithreading nascondendo al contempo gran parte degli aspetti complessi inerenti la progettazione multithreading. L'uso di una classe che supporta questo modello consente di:

  • Eseguire in background attività che richiedono una quantità eccessiva di tempo, come download e operazioni di database, senza interrompere l'applicazione.

  • Eseguire più operazioni contemporaneamente, ricevendo notifiche relative al completamento di ognuna.

  • Attendere la disponibilità delle risorse senza interrompere o bloccare l'esecuzione dell'applicazione.

  • Comunicare con operazioni asincrone in sospeso mediante un modello noto di eventi e delegati. Per altre informazioni sull'uso di gestori eventi e delegati, vedere Eventi.

Una classe che supporta il modello asincrono basato su eventi avrà uno o più metodi denominati NomeMetodoAsync. Tali metodi possono eseguire il mirroring delle versioni sincrone che eseguono la stessa operazione sul thread corrente. La classe può anche avere un evento NomeMetodoCompleted e un metodo NomeMetodoAsyncCancel (o semplicemente CancelAsync).

PictureBox è un componente tipico che supporta il modello asincrono basato su eventi. Per scaricare un'immagine in modo sincrono è possibile chiamare il relativo metodo Load, ma qualora le dimensioni dell'immagine fossero eccessive o la connessione di rete troppo lenta, l'esecuzione dell'applicazione verrà interrotta fino al completamento dell'operazione di download e alla restituzione della chiamata a Load.

Per lasciare che l'applicazione continui a essere eseguita durante il caricamento dell'immagine, è possibile chiamare il metodo LoadAsync e gestire l'evento LoadCompleted analogamente a qualsiasi altro evento. Quando si chiama il metodo LoadAsync l'esecuzione dell'applicazione procede mentre l'operazione di download continua su un altro thread, in background. Al termine dell'operazione di caricamento dell'immagine verrà chiamato il gestore eventi che potrà esaminare il parametro AsyncCompletedEventArgs per determinare se il download è stato eseguito.

Il modello asincrono basato su eventi richiede la possibilità di annullare un'operazione asincrona, requisito supportato dal controllo PictureBox per mezzo del relativo metodo CancelAsync. La chiamata del metodo CancelAsync determina l'invio di una richiesta di interruzione del download in sospeso. Quando l'attività viene annullata, viene generato l'evento LoadCompleted.

Attenzione

Il download potrebbe terminare non appena viene effettuata la richiesta di CancelAsync. In tal caso, la proprietà Cancelled potrebbe non riflettere la richiesta di annullamento. Si tratta di una race condition, ovvero di un problema riscontrato comunemente nella programmazione multithreading. Per altre informazioni sui problemi inerenti la programmazione multithreading, vedere Procedure consigliate per il threading gestito.

Caratteristiche del modello asincrono basato su eventi

Sono disponibili diversi tipi di modello asincrono basato su eventi, a seconda della complessità delle operazioni supportate da una determinata classe. Le classi più semplici possono presentare un solo metodo NomeMetodoAsync e un evento NomeMetodoCompleted corrispondente. Le classi più complesse possono invece avere diversi metodi NomeMetodoAsync, ognuno dei quali con un evento NomeMetodoCompleted associato, nonché versioni sincrone di tali metodi. Per ogni metodo asincrono le classi possono supportare funzionalità di annullamento e creazione di rapporti sullo stato di avanzamento, nonché risultati incrementali.

Un metodo asincrono può anche supportare più chiamate in sospeso, ovvero più richiami concorrenti, consentendo al codice di chiamarlo un numero indeterminato di volte prima del completamento di altre operazioni in sospeso. Una gestione efficace di questa situazione può richiedere che l'applicazione tenga traccia del completamento di ogni operazione.

Esempi del modello asincrono basato su eventi

I componenti SoundPlayer e PictureBox rappresentano implementazioni semplici del modello asincrono basato su eventi. I componenti WebClient e BackgroundWorker rappresentano implementazioni più complesse del modello in questione.

Di seguito viene riportata una dichiarazione di classe di esempio conforme al modello:

Public Class AsyncExample  
    ' Synchronous methods.  
    Public Function Method1(ByVal param As String) As Integer
    Public Sub Method2(ByVal param As Double)
  
    ' Asynchronous methods.  
    Overloads Public Sub Method1Async(ByVal param As String)
    Overloads Public Sub Method1Async(ByVal param As String, ByVal userState As Object)
    Public Event Method1Completed As Method1CompletedEventHandler  
  
    Overloads Public Sub Method2Async(ByVal param As Double)
    Overloads Public Sub Method2Async(ByVal param As Double, ByVal userState As Object)
    Public Event Method2Completed As Method2CompletedEventHandler  
  
    Public Sub CancelAsync(ByVal userState As Object)
  
    Public ReadOnly Property IsBusy () As Boolean  
  
    ' Class implementation not shown.  
End Class  
public class AsyncExample  
{  
    // Synchronous methods.  
    public int Method1(string param);  
    public void Method2(double param);  
  
    // Asynchronous methods.  
    public void Method1Async(string param);  
    public void Method1Async(string param, object userState);  
    public event Method1CompletedEventHandler Method1Completed;  
  
    public void Method2Async(double param);  
    public void Method2Async(double param, object userState);  
    public event Method2CompletedEventHandler Method2Completed;  
  
    public void CancelAsync(object userState);  
  
    public bool IsBusy { get; }  
  
    // Class implementation not shown.  
}  

La classe AsyncExample fittizia presenta due metodi che supportano entrambi i richiami sincroni e asincroni. Gli overload sincroni si comportano come qualsiasi chiamata di metodo ed eseguono le operazioni sul thread chiamante. Se l'operazione eseguita richiede tempo, potrebbe verificarsi un ritardo notevole nella restituzione della chiamata. Gli overload asincroni avviano l'operazione su un altro thread e vengono restituiti immediatamente consentendo al thread chiamante di continuare mentre l'operazione viene eseguita in background.

Overload di metodi asincroni

Esistono potenzialmente due overload per le operazioni asincrone: a richiamo singolo e a più richiami. È possibile distinguere i due tipi di overload dalle firme dei relativi metodi. Il tipo a più richiami presenta un parametro supplementare denominato userState e consente al codice di chiamare più volte Method1Async(string param, object userState) senza attendere il completamento di eventuali operazioni asincrone in sospeso. Se, invece, si prova a chiamare Method1Async(string param) prima del completamento di un richiamo precedente, il metodo genera una InvalidOperationException.

Il parametro userState degli overload a più richiami consente di distinguere le diverse operazioni asincrone. Specificare un valore univoco, ad esempio un GUID o un codice hash, per ogni chiamata al metodo Method1Async(string param, object userState). Al termine di ogni operazione, il gestore eventi potrà determinare l'istanza dell'operazione che ha generato l'evento di completamento.

Verifica delle operazioni in sospeso

Se si usano gli overload a più richiami, il codice deve tenere traccia degli oggetti userState (o ID attività) relativi alle attività in sospeso. Per ogni chiamata al metodo Method1Async(string param, object userState), di solito viene generato un nuovo oggetto userState univoco che viene aggiunto a una raccolta. Quando l'attività corrispondente a tale oggetto userState genera l'evento di completamento, l'implementazione del metodo di completamento esamina AsyncCompletedEventArgs.UserState e la rimuove dalla raccolta. Se usato in questo modo, il parametro userState funge da ID attività.

Nota

È necessario specificare un valore univoco per il parametro userState nelle chiamate a overload a più richiami. La specifica di ID attività non univoci determina la generazione di una ArgumentException da parte della classe asincrona.

Annullamento delle operazioni in sospeso

È importante che sia possibile annullare le operazioni asincrone in qualsiasi momento prima che vengano completate. Le classi che implementano il modello asincrono basato su eventi avranno un metodo CancelAsync (se è presente un solo metodo asincrono) o un metodo NomeMetodoAsyncCancel (se sono presenti più metodi asincroni).

I metodi che consentono più chiamate accettano un parametro userState che può essere usato per tenere traccia della durata di ogni attività. CancelAsync accetta un parametro userState che consente di annullare attività specifiche in sospeso.

I metodi che supportano una sola operazione in sospeso alla volta, come Method1Async(string param), non sono annullabili.

Ricezione di aggiornamenti sullo stato di avanzamento e di risultati incrementali

Per tenere traccia dello stato di avanzamento e dei risultati incrementali, una classe conforme al modello asincrono basato su eventi può facoltativamente fornire un evento, che in genere è denominato ProgressChanged o NomeMetodoProgressChanged, il cui gestore eventi accetta un parametro ProgressChangedEventArgs.

Il gestore eventi relativo all'evento ProgressChanged può esaminare la proprietà ProgressChangedEventArgs.ProgressPercentage per determinare la percentuale di completamento di un'attività asincrona. Questa proprietà ha il valore compreso tra 0 e 100 e può essere usata per aggiornare la proprietà Value di un oggetto ProgressBar. Se sono in sospeso più operazioni asincrone, è possibile usare la proprietà ProgressChangedEventArgs.UserState per identificare l'operazione per la quale viene generato il rapporto sullo stato di avanzamento.

Alcune classi possono generare un rapporto sui risultati incrementali mano a mano che procede l'esecuzione delle operazioni asincrone. I risultati verranno memorizzati in una classe derivata da ProgressChangedEventArgs e verranno visualizzati come proprietà nella classe derivata. È possibile accedere ai risultati nel gestore eventi dell'evento ProgressChanged usando la stessa procedura valida per la proprietà ProgressPercentage. Se sono in sospeso più operazioni asincrone, è possibile usare la proprietà UserState per identificare l'operazione per la quale viene generato il rapporto sui risultati incrementali.

Vedi anche