Suggerimenti per l'implementazione del modello asincrono basato su eventi

Il modello asincrono basato su eventi offre un sistema efficace per l'esposizione del comportamento asincrono nelle classi attraverso una semantica nota di eventi e delegati. Per implementare tale modello, è necessario soddisfare alcuni requisiti comportamentali specifici. Nelle sezioni riportate di seguito vengono illustrati i requisiti e le indicazioni da tenere presenti per l'implementazione di una classe che segue il modello asincrono basato su eventi.

Per informazioni generali, vedere Implementazione del modello asincrono basato su eventi.

Garanzie comportamentali richieste

Se si implementa il modello asincrono basato su eventi, è necessario fornire garanzie sul comportamento della classe e sull'affidabilità di tale comportamento per i client.

Completion

Richiamare sempre il gestore eventi MethodNameCompleted in caso di completamento, errore o annullamento. È infatti consigliabile che le applicazioni non si trovino mai in una situazione permanente di inattività nella quale il completamento non si verifica mai. Fa eccezione a questa regola il caso in cui l'operazione asincrona stessa sia progettata per non essere mai completata.

Eventi Completed e classi EventArgs

Per ogni metodo MethodNameAsync distinto, applicare i requisiti di progettazione seguenti:

  • Definire un evento MethodNameCompleted sulla stessa classe del metodo.

  • Definire una classe EventArgs e un delegato associato per l'evento MethodNameCompleted che deriva dalla classe AsyncCompletedEventArgs. Il nome di classe predefinito deve essere nel formato NomeMetodoCompletedEventArgs.

  • Verificare che la classe EventArgs sia specifica dei valori restituiti del metodo NomeMetodo. Quando si usa la classe EventArgs, è consigliabile non richiedere mai agli sviluppatori di eseguire il cast del risultato.

    Nell'esempio di codice seguente vengono illustrate rispettivamente un'implementazione valida e non valida del requisito di progettazione in questione.

// Good design
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e)
{
    DemoType result = e.Result;
}

// Bad design
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e)
{
    DemoType result = (DemoType)(e.Result);
}
  • Non definire una classe EventArgs per restituire metodi che restituiscono void. Usare invece un'istanza della classe AsyncCompletedEventArgs.

  • Assicurarsi di generare sempre l'evento NomeMetodoCompleted. Questo evento deve essere generato in caso di corretto completamento, errore o annullamento. È infatti consigliabile che le applicazioni non si trovino mai in una situazione permanente di inattività nella quale il completamento non si verifica mai.

  • Assicurarsi di intercettare le eccezioni che si verificano nell'operazione asincrona e assegnare l'eccezione intercettata alla proprietà Error.

  • Se si è verificato un errore durante il completamento dell'attività, è possibile che i risultati non siano accessibili. Quando la proprietà Error non è null, assicurarsi che l'accesso a qualsiasi proprietà nella struttura EventArgs generi un'eccezione. Usare il metodo RaiseExceptionIfNecessary per eseguire questa verifica.

  • Modellare un timeout come errore. Quando si verifica un timeout, generare l'evento NomeMetodoCompleted e assegnare un oggetto TimeoutException alla proprietà Error.

  • Se la classe supporta più chiamate simultanee, assicurarsi che l'evento NomeMetodoCompleted contenga l'oggetto userSuppliedState appropriato.

  • Assicurarsi che l'evento NomeMetodoCompleted venga generato nel thread appropriato e nel momento adeguato del ciclo di vita dell'applicazione. Per altre informazioni, vedere la sezione Thread e contesti.

Esecuzione simultanea di operazioni

  • Se la classe supporta più chiamate simultanee, consentire allo sviluppatore di tenere traccia di ogni chiamata separatamente definendo l'overload di NomeMetodoAsync che accetta un parametro di stato con valori di oggetto, o un ID attività, denominato userSuppliedState. Questo parametro deve essere sempre l'ultimo nella firma del metodo NomeMetodoAsync.

  • Se la classe definisce l'overload di NomeMetodoAsync che accetta un parametro di stato con valori di oggetto, o un ID attività, tenere traccia del ciclo di vita dell'operazione con tale ID e specificarlo nuovamente nel gestore del completamento. Per l'esecuzione di tali operazioni sono disponibili classi di supporto specifiche. Per altre informazioni sulla gestione della concorrenza, vedere Procedura: implementare un componente che supporta il modello asincrono basato su eventi.

  • Se la classe definisce il metodo NomeMetodoAsync senza il parametro di stato e non supporta più chiamate simultanee, assicurarsi che qualsiasi tentativo di richiamare NomeMetodoAsync prima del completamento della chiamata a NomeMetodoAsync precedente generi un'eccezione di tipo InvalidOperationException.

  • Di norma, non generare un'eccezione se il metodo NomeMetodoAsync senza il parametro userSuppliedState viene richiamato più volte determinando la presenza di più operazioni in attesa. È possibile generare un'eccezione quando la classe non può gestire questa situazione, ma si presuppone che gli sviluppatori possano gestire i diversi callback indistinguibili in corso.

Accesso ai risultati

Report di stato

  • Garantire il supporto della generazione di report sullo stato di avanzamento, se possibile, per consentire agli sviluppatori di offrire prestazioni ottimali dell'applicazione durante l'utilizzo della classe.

  • Se si implementa un evento ProgressChanged o NomeMetodoProgressChanged, verificare che non siano stati generati eventi di questo tipo per una determinata operazione asincrona dopo la generazione dell'evento NomeMetodoCompleted.

  • In caso di popolamento della classe ProgressChangedEventArgs standard, accertarsi che la proprietà ProgressPercentage possa essere sempre interpretata come percentuale. Non è necessario che il valore di percentuale sia preciso, purché si tratti di un valore di percentuale. Se l'unità di misura dei report sullo stato di avanzamento deve essere diversa da una percentuale, derivare una classe dalla classe ProgressChangedEventArgs e lasciare impostato su 0 il valore di ProgressPercentage. Evitare di usare unità di misura dei report diverse dalla percentuale.

  • Verificare che l'evento ProgressChanged venga generato sul thread appropriato nel momento adeguato del ciclo di vita dell'applicazione. Per altre informazioni, vedere la sezione Thread e contesti.

Implementazione della proprietà IsBusy

  • Non esporre una proprietà IsBusy se la classe supporta più chiamate simultanee. I proxy dei servizi Web XML, ad esempio, non espongono una proprietà IsBusy in quanto supportano più chiamate simultanee dei metodi asincroni.

  • La proprietà IsBusy deve restituire true dopo la chiamata al metodo NomeMetodoAsync e prima della generazione dell'evento NomeMetodoCompleted. In caso contrario, deve restituire false. I componenti BackgroundWorker e WebClient sono esempi di classi che espongono una proprietà IsBusy.

Annullamento

  • Garantire il supporto dell'annullamento, se possibile, per consentire agli sviluppatori di offrire prestazioni ottimali dell'applicazione durante l'utilizzo della classe.

  • In caso di annullamento, impostare il flag Cancelled nell'oggetto AsyncCompletedEventArgs.

  • Assicurarsi che i tentativi di accesso al risultato generino un'eccezione di tipo InvalidOperationException che segnala l'avvenuto annullamento dell'operazione. Usare il metodo AsyncCompletedEventArgs.RaiseExceptionIfNecessary per eseguire questa verifica.

  • Verificare che le chiamate a un metodo di annullamento vengano eseguite correttamente senza mai generare un'eccezione. Generalmente, a un client viene notificato se un'operazione è realmente annullabile in qualsiasi momento mentre non viene notificato se un annullamento già avviato ha avuto esito positivo. All'applicazione viene invece sempre notificato l'avvenuto annullamento, poiché lo stato di completamento dipende anche dall'applicazione.

  • Generare l'evento NomeMetodoCompleted quando l'operazione viene annullata.

Errori ed eccezioni

  • Intercettare le eccezioni che si verificano nell'operazione asincrona e impostare il valore della proprietà AsyncCompletedEventArgs.Error su tali eccezioni.

Thread e contesti

Per un corretto funzionamento della classe, è fondamentale che i gestori eventi del client siano chiamati sul thread o sul contesto appropriato per il modello di applicazione specifico, incluse le applicazioni ASP.NET e Windows Forms. Per garantire il comportamento corretto della classe asincrona con qualsiasi modello di applicazione sono disponibili due classi di supporto, AsyncOperation e AsyncOperationManager.

AsyncOperationManager fornisce un metodo, CreateOperation, che restituisce un oggetto AsyncOperation. Il metodo NomeMetodoAsync chiama CreateOperation e la classe usa l'oggetto AsyncOperation restituito per tenere traccia del ciclo di vita dell'attività asincrona.

Per generare report destinati al client sullo stato di avanzamento, sui risultati incrementali e sul completamento, chiamare i metodi Post e OperationCompleted sull'oggetto AsyncOperation. AsyncOperation è responsabile del marshalling delle chiamate ai gestori eventi del client al thread o al contesto appropriato.

Nota

È possibile aggirare queste regole se si vuole esplicitamente evitare l'uso dei criteri del modello di applicazione, pur usufruendo degli altri vantaggi derivanti dal modello asincrono basato su eventi. È ad esempio possibile creare una classe threading Free operante in Windows Form, purché agli sviluppatori siano chiare le restrizioni implicate. Le applicazioni console non sincronizzano l'esecuzione delle chiamate del metodo Post. L'ordine di generazione degli eventi ProgressChanged potrebbe quindi non essere corretto. Se si vuole l'esecuzione serializzata delle chiamate al metodo Post, implementare e installare una classe System.Threading.SynchronizationContext.

Per altre informazioni sull'uso di AsyncOperation e AsyncOperationManager per abilitare le operazioni asincrone, vedere Procedura: implementare un componente che supporta il modello asincrono basato su eventi.

Linee guida

  • È preferibile che ogni chiamata a un metodo sia indipendente dalle altre. Si consiglia di evitare di associare chiamate a risorse condivise. Se le risorse devono essere condivise tra le chiamate, è necessario fornire un meccanismo di sincronizzazione adeguato nell'implementazione.

  • Non sono consigliati progetti in cui al client sia richiesta l'implementazione della funzionalità di sincronizzazione. Si supponga il caso di un metodo asincrono che riceve un oggetto statico globale come parametro. Più chiamate simultanee a tale metodo potrebbero determinare il danneggiamento dei dati o deadlock.

  • Se si implementa un metodo con l'overload a più chiamate (userState nella firma), la classe dovrà gestire un insieme di stati utente, o ID attività, e le relative operazioni in sospeso. Questa raccolta deve essere protetta con aree lock, in quanto le varie chiamate aggiungono e rimuovono gli oggetti userState presenti al suo interno.

  • Riutilizzare le classi CompletedEventArgs, quando possibile e appropriato. In tal caso, la denominazione non sarà coerente con il nome del metodo, poiché un determinato delegato e il tipo EventArgs non saranno collegati a un unico metodo. Non è tuttavia accettabile imporre agli sviluppatori di eseguire il cast del valore recuperato da una proprietà sull'oggetto EventArgs.

  • Se si sta creando una classe che deriva da Component, non implementare e installare la propria classe SynchronizationContext. Sono i modelli di applicazione e non i componenti a controllare l'oggetto SynchronizationContext usato.

  • L'uso di qualsiasi tipo di multithreading determina la potenziale esposizione a bug seri e complessi. Prima di implementare una soluzione che preveda l'uso del multithreading, vedere Procedure consigliate per l'uso del threading gestito.

Vedi anche