Condividi tramite


Implementazione del modello asincrono basato su eventi

Se si scrive una classe con alcune operazioni che potrebbero causare ritardi evidenti, valutare la possibilità di fornire funzionalità asincrone implementando il modello asincrono basato su eventi.

Il modello asincrono basato su eventi offre un modo standardizzato per creare un pacchetto di una classe con funzionalità asincrone. Se implementata con classi helper come AsyncOperationManager, la classe funzionerà correttamente in qualsiasi modello di applicazione, tra cui ASP.NET, applicazioni console e applicazioni Windows Form.

Per un esempio che implementa il modello asincrono basato su eventi, vedere Procedura: Implementare un componente che supporta il modello asincrono basato su eventi.

Per operazioni asincrone semplici, è possibile trovare il BackgroundWorker componente adatto. Per altre informazioni su BackgroundWorker, vedere Procedura: Eseguire un'operazione in background.

Nell'elenco seguente vengono descritte le funzionalità del modello asincrono basato su eventi illustrato in questo argomento.

  • Opportunità di implementazione del modello asincrono basato su eventi

  • Denominazione di metodi asincroni

  • Supportare l'annullamento facoltativamente

  • Supportare opzionalmente la proprietà IsBusy

  • Facoltativamente, fornire supporto per la creazione di report sullo stato di avanzamento

  • Facoltativamente, fornire supporto per la restituzione di risultati incrementali

  • Gestione dei parametri Out e Ref nei metodi

Opportunità di implementazione del modello asincrono basato su eventi

Prendere in considerazione l'implementazione del modello asincrono basato su eventi quando:

  • I client della tua classe non hanno bisogno che gli oggetti WaitHandle e IAsyncResult siano disponibili per le operazioni asincrone, il che significa che il polling e WaitAll o WaitAny dovranno essere implementati dal client.

  • Si vuole che le operazioni asincrone vengano gestite dal client con il familiare modello delegati/eventi.

Qualsiasi operazione è un candidato per un'implementazione asincrona, ma quelle che ci si aspetta possano avere lunghe latenze dovrebbero essere prese in considerazione. Particolarmente appropriato sono le operazioni in cui i client chiamano un metodo e ricevono una notifica al completamento, senza ulteriori interventi necessari. Sono anche appropriate operazioni che vengono eseguite continuamente, notificando periodicamente ai client lo stato di avanzamento, i risultati incrementali o le modifiche dello stato.

Per altre informazioni sulla scelta di quando supportare il modello asincrono basato su eventi, vedere Decidere quando implementare il modello asincrono basato su eventi.

Denominazione di metodi asincroni

Per ogni metodo sincrono MethodName per cui si vuole fornire una controparte asincrona:

Definire un metodo AsincronoNomeMetodo che:

  • Restituisce void.

  • Accetta gli stessi parametri del metodo MethodName .

  • Accetta più chiamate.

Definire facoltativamente un overload NomeMetodoAsincrono, identico a NomeMetodoAsincrono, ma con un parametro aggiuntivo denominato e con un valore di tipo oggetto userState. Eseguire questa operazione se si è pronti a gestire più chiamate simultanee del metodo, nel qual caso il userState valore verrà recapitato a tutti i gestori eventi per distinguere le chiamate del metodo. È anche possibile scegliere di eseguire questa operazione semplicemente come posizione in cui archiviare lo stato utente per il recupero successivo.

Per ogni firma del metodo asincronoNomeMetodo separato:

  1. Definire l'evento seguente nella stessa classe del metodo :

    Public Event MethodNameCompleted As MethodNameCompletedEventHandler
    
    public event MethodNameCompletedEventHandler MethodNameCompleted;
    
  2. Definire il seguente delegato e AsyncCompletedEventArgs. Questi verranno probabilmente definiti all'esterno della classe stessa, ma nello stesso spazio dei nomi.

    Public Delegate Sub MethodNameCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As MethodNameCompletedEventArgs)
    
    Public Class MethodNameCompletedEventArgs
        Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As MyReturnType
    End Property
    
    public delegate void MethodNameCompletedEventHandler(object sender,
        MethodNameCompletedEventArgs e);
    
    public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
    {
        public MyReturnType Result { get; }
    }
    
    • Verificare che la classe MethodNameCompletedEventArgs esponga i relativi membri come proprietà di sola lettura e non campi, in quanto i campi impediscono il data binding.

    • Non definire classi derivate AsyncCompletedEventArgs per i metodi che non producono risultati. È sufficiente usare un'istanza di AsyncCompletedEventArgs se stessa.

      Annotazioni

      È perfettamente accettabile, quando possibile e appropriato, riutilizzare i delegati e i tipi AsyncCompletedEventArgs. In questo caso, la denominazione non sarà coerente con il nome del metodo, perché un delegato specificato e AsyncCompletedEventArgs non sarà associato a un singolo metodo.

Supportare l'annullamento facoltativamente

Se la classe supporta l'annullamento delle operazioni asincrone, l'annullamento deve essere esposto al client come descritto di seguito. Prima di definire il supporto per l'annullamento, è necessario raggiungere due punti decisionali:

  • La tua classe, incluse le future aggiunte previste, ha una sola operazione asincrona che supporta l'annullamento?
  • Le operazioni asincrone che supportano l'annullamento possono gestire più operazioni in corso? Ovvero, il metodo MethodNameAsync accetta un userState parametro e consente più chiamate prima di attendere il completamento?

Usare le risposte a queste due domande nella tabella seguente per determinare come dovrebbe essere la firma del metodo di annullamento.

Visual Basic

Più operazioni simultanee supportate Una sola operazione alla volta
Un'operazione asincrona nell'intera classe Sub MethodNameAsyncCancel(ByVal userState As Object) Sub MethodNameAsyncCancel()
Più operazioni asincrone nella classe Sub CancelAsync(ByVal userState As Object) Sub CancelAsync()

C#

Più operazioni simultanee supportate Una sola operazione alla volta
Un'operazione asincrona nell'intera classe void MethodNameAsyncCancel(object userState); void MethodNameAsyncCancel();
Più operazioni asincrone nella classe void CancelAsync(object userState); void CancelAsync();

Se si definisce il CancelAsync(object userState) metodo, i client devono prestare attenzione quando si scelgono i valori di stato per renderli in grado di distinguere tra tutti i metodi asincroni richiamati sull'oggetto e non solo tra tutte le chiamate di un singolo metodo asincrono.

La decisione di denominare la versione dell'operazione asincrona singola MethodNameAsyncCancel è basata sulla facilità di individuare il metodo in un ambiente di sviluppo come IntelliSense di Visual Studio. Questo raggruppa i membri correlati e li distingue da altri membri che non hanno nulla a che fare con la funzionalità asincrona. Se si prevede di aggiungere altre operazioni asincrone nelle versioni successive, è preferibile definire CancelAsync.

Non definire più metodi dalla tabella precedente nella stessa classe. Questo non avrà senso, oppure ingombrerà l'interfaccia della classe con una proliferazione di metodi.

Questi metodi in genere restituiscono immediatamente, e l'operazione potrebbe essere annullata effettivamente o meno. Nel gestore eventi per l'evento MethodNameCompleted l'oggetto MethodNameCompletedEventArgs contiene un Cancelled campo che i client possono utilizzare per determinare se si è verificato l'annullamento.

Rispettare la semantica di annullamento descritta in Procedure consigliate per l'implementazione del modello asincrono basato su eventi.

Supportare opzionalmente la proprietà IsBusy

Se la classe non supporta più chiamate simultanee, è consigliabile esporre una IsBusy proprietà. Ciò consente agli sviluppatori di determinare se un metodo MethodNameAsync è in esecuzione senza intercettare un'eccezione dal metodo Async MethodName.

Rispettare la IsBusy semantica descritta in Procedure consigliate per l'implementazione del modello asincrono basato su eventi.

Facoltativamente, fornire supporto per la creazione di report sullo stato di avanzamento

È spesso consigliabile per un'operazione asincrona segnalare lo stato di avanzamento durante l'operazione. Il modello asincrono basato su eventi fornisce una linea guida per farlo.

  • Facoltativamente, definire un evento da creare dall'operazione asincrona e richiamare sul thread appropriato. L'oggetto ProgressChangedEventArgs contiene un indicatore di stato con valori interi che dovrebbe essere compreso tra 0 e 100.

  • Denominare questo evento nel modo seguente:

    • ProgressChanged se la classe dispone di più operazioni asincrone (o si prevede che l'aumento includa più operazioni asincrone nelle versioni future);

    • MethodNameProgressChanged se la classe ha una singola operazione asincrona.

    Questa scelta di denominazione è parallela a quella effettuata per il metodo di annullamento, come descritto nella sezione Opzionalmente Supporto per Annullamento.

Questo evento deve usare la firma del delegato ProgressChangedEventHandler e la classe ProgressChangedEventArgs. In alternativa, se è possibile specificare un indicatore di stato più specifico del dominio (ad esempio, byte letti e totali byte per un'operazione di download), è necessario definire una classe derivata di ProgressChangedEventArgs.

Si noti che per la classe è presente un solo evento ProgressChanged come MethodNameProgressChanged, indipendentemente dal numero di metodi asincroni che supporta. È previsto che i client usino l'oggetto userState passato ai metodi MethodNameAsync per distinguere tra gli aggiornamenti di avanzamento in più operazioni concorrenti.

Possono verificarsi situazioni in cui più operazioni supportano lo stato di avanzamento e ognuna restituisce un indicatore diverso per lo stato di avanzamento. In questo caso, un singolo ProgressChanged evento non è appropriato ed è possibile considerare il supporto di più ProgressChanged eventi. In questo caso usare un modello di denominazione MethodNameProgressChanged per ogni metodo MethodNameAsync .

Rispettare la semantica di report sul progresso descritta nelle Best Practices per l'implementazione del modello asincrono basato su eventi.

Facoltativamente, fornire supporto per la restituzione di risultati incrementali

In alcuni casi un'operazione asincrona può restituire risultati incrementali prima del completamento. Per supportare questo scenario è possibile usare diverse opzioni. Di seguito sono riportati alcuni esempi.

Classe a singola operazione

Se la classe supporta solo una singola operazione asincrona e tale operazione è in grado di restituire risultati incrementali, allora:

  • Estendere il ProgressChangedEventArgs tipo per trasportare i dati dei risultati incrementali e definire un evento MethodNameProgressChanged con questi dati estesi.

  • Generare questo evento MethodNameProgressChanged quando è presente un risultato incrementale da segnalare.

Questa soluzione si applica in modo specifico a una classe a singola operazione asincrona perché non esiste alcun problema con lo stesso evento che si verifica per restituire risultati incrementali su "tutte le operazioni", come avviene con l'evento MethodNameProgressChanged .

Classe a più operazioni con risultati incrementali omogenei

In questo caso, la classe supporta più metodi asincroni, ognuno in grado di restituire risultati incrementali e questi risultati incrementali hanno tutti lo stesso tipo di dati.

Seguire il modello descritto in precedenza per le classi a operazione singola, perché la stessa EventArgs struttura funzionerà per tutti i risultati incrementali. Definire un ProgressChanged evento anziché un evento MethodNameProgressChanged , poiché si applica a più metodi asincroni.

Classe a più operazioni con risultati incrementali eterogenei

Se la classe supporta più metodi asincroni, ognuno dei quali restituisce un tipo di dati diverso, è necessario:

  • Separare la segnalazione dei risultati incrementali dalla creazione di report sullo stato di avanzamento.

  • Definire un evento MethodNameProgressChanged separato con appropriato EventArgs per ogni metodo asincrono per gestire i dati incrementali dei risultati del metodo.

Richiamare il gestore eventi nel thread appropriato, come descritto in Procedure consigliate per l'implementazione del modello asincrono basato su eventi.

Gestione dei parametri Out e Ref nei metodi

Anche se l'uso di out e ref è, in generale, sconsigliato in .NET, ecco le regole da seguire quando sono presenti:

Dato un metodo sincrono MethodName:

  • out I parametri per MethodName non devono far parte di MethodNameAsync. Devono invece far parte di MethodNameCompletedEventArgs con lo stesso nome del relativo parametro equivalente in MethodName (a meno che non esista un nome più appropriato).

  • ref I parametri di MethodName devono essere visualizzati come parte di MethodNameAsync e come parte di MethodNameCompletedEventArgs con lo stesso nome del relativo parametro equivalente in NomeMetodo (a meno che non sia presente un nome più appropriato).

Si consideri ad esempio di avere una situazione simile alla seguente:

Public Function MethodName(ByVal arg1 As String, ByRef arg2 As String, ByRef arg3 As String) As Integer
public int MethodName(string arg1, ref string arg2, out string arg3);

Il metodo asincrono e la relativa AsyncCompletedEventArgs classe sono simili al seguente:

Public Sub MethodNameAsync(ByVal arg1 As String, ByVal arg2 As String)

Public Class MethodNameCompletedEventArgs
    Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As Integer
    End Property
    Public ReadOnly Property Arg2() As String
    End Property
    Public ReadOnly Property Arg3() As String
    End Property
End Class
public void MethodNameAsync(string arg1, string arg2);

public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
    public int Result { get; };
    public string Arg2 { get; };
    public string Arg3 { get; };
}

Vedere anche