Condividi tramite


Modello di programmazione asincrona attività

È possibile evitare colli di bottiglia delle prestazioni e migliorare la velocità di risposta complessiva dell'applicazione usando la programmazione asincrona. Tuttavia, le tecniche tradizionali per la scrittura di applicazioni asincrone possono essere complesse, rendendo difficile scrivere, eseguire il debug e gestire.

C# supporta l'approccio semplificato, la programmazione asincrona, che sfrutta il supporto asincrono nel runtime .NET. Il compilatore esegue il lavoro difficile usato dallo sviluppatore e l'applicazione mantiene una struttura logica simile a codice sincrono. Di conseguenza, si ottengono tutti i vantaggi della programmazione asincrona con una frazione dello sforzo.

Questo argomento offre una panoramica di quando e come usare la programmazione asincrona e include collegamenti a argomenti di supporto che contengono dettagli ed esempi.

Async migliora la velocità di risposta

Asincronia è essenziale per le attività potenzialmente bloccabili, ad esempio l'accesso Web. L'accesso a una risorsa Web a volte è lento o ritardato. Se tale attività è bloccata in un processo sincrono, l'intera applicazione deve attendere. In un processo asincrono, l'applicazione può continuare con altre operazioni che non dipendono dalla risorsa Web fino al termine dell'attività di blocco potenziale.

La tabella seguente illustra le aree tipiche in cui la programmazione asincrona migliora la velocità di risposta. Le API elencate da .NET e Windows Runtime contengono metodi che supportano la programmazione asincrona.

Area dell'applicazione Tipi .NET con metodi asincroni Tipi di Windows Runtime con metodi asincroni
Accesso Web HttpClient Windows.Web.Http.HttpClient
SyndicationClient
Uso dei file JsonSerializer
StreamReader
StreamWriter
XmlReader
XmlWriter
StorageFile
Uso delle immagini MediaCapture
BitmapEncoder
BitmapDecoder
Programmazione WCF operazioni sincrone e asincrone

L'asincronia si dimostra particolarmente utile per le applicazioni che accedono al thread dell'interfaccia utente perché tutte le attività relative all'interfaccia utente condividono in genere un thread. Se un processo viene bloccato in un'applicazione sincrona, tutti vengono bloccati. L'applicazione smette di rispondere e si potrebbe concludere che non è riuscita quando è invece in attesa.

Quando si usano metodi asincroni, l'applicazione continua a rispondere all'interfaccia utente. È possibile ridimensionare o ridurre al minimo una finestra, ad esempio, oppure chiudere l'applicazione se non si vuole attendere il completamento.

L'approccio asincrono aggiunge l'equivalente di una trasmissione automatica all'elenco di opzioni tra cui è possibile scegliere durante la progettazione di operazioni asincrone. Ciò significa che si ottengono tutti i vantaggi della programmazione asincrona tradizionale, ma con molto meno impegno da parte dello sviluppatore.

I metodi asincroni sono facili da scrivere

Le parole chiave async e await in C# sono il fulcro della programmazione asincrona. Usando queste due parole chiave, è possibile usare le risorse in .NET Framework, .NET Core o Windows Runtime per creare un metodo asincrono quasi facilmente come si crea un metodo sincrono. I metodi asincroni definiti usando la parola chiave async vengono definiti metodi asincroni.

Nell'esempio seguente viene illustrato un metodo asincrono. Quasi tutto il codice dovrebbe sembrare familiare.

È possibile trovare un esempio completo di Windows Presentation Foundation (WPF), disponibile per il download da programma asincrono con async e await in C#.

public async Task<int> GetUrlContentLengthAsync()
{
    using var client = new HttpClient();

    Task<string> getStringTask =
        client.GetStringAsync("https://learn.microsoft.com/dotnet");

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}

Dall'esempio precedente, è possibile apprendere diverse pratiche. Iniziare con la firma del metodo. Include il modificatore async. Il tipo restituito è Task<int> (vedere la sezione "Tipi restituiti" per altre opzioni). Il nome del metodo termina in Async. Nel corpo del metodo GetStringAsync restituisce un Task<string>. Questo significa che quando completi await l'attività, otterrai un string (contents). Prima di attendere l'attività, è possibile eseguire operazioni che non si basano sul string da GetStringAsync.

Prestare particolare attenzione all'operatore await. Sospende GetUrlContentLengthAsync:

  • GetUrlContentLengthAsync non può continuare fino al completamento dell'getStringTask.
  • Nel frattempo, il controllo torna al chiamante di GetUrlContentLengthAsync.
  • Il controllo riprende qui quando getStringTask è completato.
  • L'operatore await recupera quindi il risultato del string da getStringTask.

L'istruzione return specifica un risultato integer. Tutti i metodi che attendono GetUrlContentLengthAsync recuperano il valore di lunghezza.

Se GetUrlContentLengthAsync non ha alcun lavoro che può eseguire tra la chiamata GetStringAsync e l'attesa del completamento, è possibile semplificare il codice chiamando e attendendo nella singola istruzione seguente.

string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");

Le caratteristiche seguenti riepilogano ciò che rende l'esempio precedente un metodo asincrono:

  • La firma del metodo include un modificatore async.

  • Il nome di un metodo asincrono, per convenzione, termina con un suffisso "Async".

  • Il tipo restituito è uno dei tipi seguenti:

    • Task<TResult> se il metodo ha un'istruzione di return in cui l'operando ha tipo TResult.
    • Task se il metodo non ha un'istruzione return o ha un'istruzione return senza operando.
    • void se stai scrivendo un gestore di eventi asincrono.
    • Qualsiasi altro tipo con un metodo GetAwaiter.

    Per ulteriori informazioni, vedere la sezione tipi e parametri restituiti.

  • Il metodo include in genere almeno un'espressione await, che contrassegna un punto in cui il metodo non può continuare fino al completamento dell'operazione asincrona attesa. Nel frattempo, il metodo viene sospeso e il controllo torna al chiamante del metodo. La sezione successiva di questo argomento illustra cosa accade al punto di sospensione.

Nei metodi asincroni si usano le parole chiave e i tipi forniti per indicare le operazioni da eseguire e il compilatore esegue il resto, incluso tenere traccia di ciò che deve accadere quando il controllo torna a un punto await in un metodo sospeso. Alcuni processi di routine, ad esempio cicli e gestione delle eccezioni, possono essere difficili da gestire nel codice asincrono tradizionale. In un metodo asincrono questi elementi vengono scritti in modo analogo a quanto si farebbe in una soluzione sincrona e il problema viene risolto.

Per altre informazioni sull'asincronità nelle versioni precedenti di .NET Framework, vedere TPL e la programmazione asincrona tradizionale di .NET Framework.

Cosa accade in un metodo asincrono

La cosa più importante da comprendere nella programmazione asincrona è il modo in cui il flusso di controllo passa dal metodo al metodo. Il diagramma seguente illustra il processo:

tracciare la navigazione del flusso di controllo asincrono

I numeri nel diagramma corrispondono ai passaggi seguenti, avviati quando un metodo chiamante chiama il metodo asincrono.

  1. Un metodo chiamante invoca e attende il metodo asincrono GetUrlContentLengthAsync.

  2. GetUrlContentLengthAsync crea un'istanza di HttpClient e chiama il metodo asincrono GetStringAsync per scaricare il contenuto di un sito Web come stringa.

  3. Qualcosa accade in GetStringAsync che sospende il suo progresso. Forse deve attendere il download di un sito Web o un'altra attività di blocco. Per evitare di bloccare le risorse, GetStringAsync restituisce il controllo al chiamante, GetUrlContentLengthAsync.

    GetStringAsync restituisce un Task<TResult>, dove TResult è una stringa e GetUrlContentLengthAsync assegna l'attività alla variabile getStringTask. L'attività rappresenta il processo in corso per la chiamata a GetStringAsync, con l'impegno di produrre un valore stringa reale al termine del lavoro.

  4. Poiché getStringTask non è stato ancora atteso, GetUrlContentLengthAsync può continuare con altri lavori che non dipendono dal risultato finale di GetStringAsync. Tale lavoro è rappresentato da una chiamata al metodo sincrono DoIndependentWork.

  5. DoIndependentWork è un metodo sincrono che esegue il lavoro e restituisce al chiamante.

  6. GetUrlContentLengthAsync ha esaurito il lavoro che può eseguire senza un risultato da getStringTask. GetUrlContentLengthAsync successivamente vuole calcolare e restituire la lunghezza della stringa scaricata, ma il metodo non può calcolare tale valore finché il metodo non ha la stringa.

    Pertanto, GetUrlContentLengthAsync usa un operatore await per sospendere lo stato di avanzamento e restituire il controllo al metodo che ha chiamato GetUrlContentLengthAsync. GetUrlContentLengthAsync restituisce un Task<int> al chiamante. L'attività rappresenta una promessa per produrre un risultato integer che corrisponde alla lunghezza della stringa scaricata.

    Nota

    Se GetStringAsync (e quindi getStringTask) viene completato prima che GetUrlContentLengthAsync lo attenda, il controllo rimane in GetUrlContentLengthAsync. La spesa per la sospensione e quindi il ritorno a GetUrlContentLengthAsync sarebbe sprecato se il processo asincrono chiamato getStringTask è già stato completato e GetUrlContentLengthAsync non deve attendere il risultato finale.

    All'interno del metodo chiamante, il pattern di elaborazione continua. Il chiamante potrebbe eseguire altre operazioni che non dipendono dal risultato di GetUrlContentLengthAsync prima di attendere tale risultato oppure il chiamante potrebbe attendere immediatamente. Il metodo chiamante è in attesa di GetUrlContentLengthAsynce GetUrlContentLengthAsync è in attesa di GetStringAsync.

  7. GetStringAsync completa e produce un risultato sotto forma di stringa. Il risultato della stringa non viene restituito dalla chiamata a GetStringAsync nel modo previsto. Tenere presente che il metodo ha già restituito un'attività nel passaggio 3. Il risultato della stringa viene invece archiviato nell'attività che rappresenta il completamento del metodo getStringTask. L'operatore await recupera il risultato da getStringTask. L'istruzione di assegnazione assegna il risultato recuperato a contents.

  8. Quando GetUrlContentLengthAsync ha il risultato della stringa, il metodo può calcolare la lunghezza della stringa. Il lavoro di GetUrlContentLengthAsync viene quindi completato e il gestore di eventi in attesa può riprendere. Nell'esempio completo alla fine dell'argomento è possibile verificare che il gestore eventi recuperi e stampi il valore del risultato della lunghezza. Se sei nuovo alla programmazione asincrona, prenditi un momento per considerare la differenza tra il comportamento sincrono e quello asincrono. Un metodo sincrono restituisce quando il lavoro è completo (passaggio 5), ma un metodo asincrono restituisce un valore di attività quando il lavoro viene sospeso (passaggi 3 e 6). Quando il metodo asincrono completa il proprio lavoro, l'attività viene contrassegnata come completata e il risultato, se presente, viene archiviato nell'attività.

Metodi asincroni dell'API

Ci si potrebbe chiedere dove trovare metodi come GetStringAsync che supportano la programmazione asincrona. .NET Framework 4.5 o versione successiva e .NET Core contengono molti membri che funzionano con async e await. È possibile riconoscerli dal suffisso "Async" aggiunto al nome del membro e dal tipo restituito di Task o Task<TResult>. Ad esempio, la classe System.IO.Stream contiene metodi come CopyToAsync, ReadAsynce WriteAsync insieme ai metodi sincroni CopyTo, Reade Write.

Windows Runtime contiene anche molti metodi che è possibile usare con async e await nelle app di Windows. Per ulteriori informazioni, vedere Threading e programmazione asincrona per lo sviluppo UWP, e Programmazione asincrona (app di Windows Store) e Introduzione: Chiamata di API asincrone in C# o Visual Basic se si usano versioni precedenti di Windows Runtime.

Discussioni

I metodi asincroni devono essere operazioni non bloccabili. Un'espressione await in un metodo asincrono non blocca il thread corrente mentre l'attività attesa è in esecuzione. Invece, l'espressione registra il resto del metodo come continuazione e restituisce il controllo al chiamante del metodo asincrono.

Le parole chiave async e await non causano la creazione di thread aggiuntivi. I metodi asincroni non richiedono il multithreading perché un metodo asincrono non viene eseguito nel proprio thread. Il metodo viene eseguito nel contesto di sincronizzazione corrente e usa l'ora nel thread solo quando il metodo è attivo. È possibile usare Task.Run per spostare il lavoro associato alla CPU in un thread in background, ma un thread in background non è utile per un processo in attesa che i risultati diventino disponibili.

L'approccio asincrono alla programmazione asincrona è preferibile agli approcci esistenti in quasi ogni caso. In particolare, questo approccio è migliore rispetto alla classe BackgroundWorker per le operazioni associate a I/O perché il codice è più semplice e non è necessario proteggersi da condizioni di competizione. In combinazione con il metodo Task.Run, la programmazione asincrona è migliore di BackgroundWorker per le operazioni associate alla CPU perché la programmazione asincrona separa i dettagli di coordinamento dell'esecuzione del codice dal lavoro che Task.Run trasferisce al pool di thread.

async (asincrono) e await (attendi)

Se si specifica che un metodo è un metodo asincrono usando il modificatore asincrono, si abilitano le seguenti due funzionalità.

  • Il metodo asincrono contrassegnato può usare await per designare i punti di sospensione. L'operatore await indica al compilatore che il metodo asincrono non può continuare fino al completamento del processo asincrono atteso. Nel frattempo, il controllo torna al chiamante del metodo asincrono.

    La sospensione di un metodo asincrono in un'espressione await non costituisce un'uscita dal metodo, e i blocchi finally non vengono eseguiti.

  • Il metodo asincrono contrassegnato può essere atteso dai metodi che lo chiamano.

Un metodo asincrono contiene in genere una o più occorrenze di un operatore await, ma l'assenza di espressioni await non causa un errore del compilatore. Se un metodo asincrono non usa un operatore await per contrassegnare un punto di sospensione, il metodo viene eseguito come metodo sincrono, nonostante il modificatore async. Il compilatore genera un avviso per tali metodi.

async e await sono parole chiave contestuali. Per altre informazioni ed esempi, vedere gli argomenti seguenti:

Tipi e parametri restituiti

Un metodo asincrono restituisce in genere un Task o un Task<TResult>. All'interno di un metodo asincrono, un operatore await viene applicato a un'attività restituita da una chiamata a un altro metodo asincrono.

Specificare Task<TResult> come tipo restituito se il metodo contiene un'istruzione return che specifica un operando di tipo TResult.

Si usa Task come tipo restituito se il metodo non ha un'istruzione return o ha un'istruzione return che non restituisce un operando.

È anche possibile specificare qualsiasi altro tipo restituito, purché il tipo includa un metodo GetAwaiter. ValueTask<TResult> è un esempio di tale tipo. È disponibile nel pacchetto System.Threading.Tasks.Extension NuGet.

Nell'esempio seguente viene illustrato come dichiarare e chiamare un metodo che restituisce un Task<TResult> o un Task:

async Task<int> GetTaskOfTResultAsync()
{
    int hours = 0;
    await Task.Delay(0);

    return hours;
}


Task<int> returnedTaskTResult = GetTaskOfTResultAsync();
int intResult = await returnedTaskTResult;
// Single line
// int intResult = await GetTaskOfTResultAsync();

async Task GetTaskAsync()
{
    await Task.Delay(0);
    // No return statement needed
}

Task returnedTask = GetTaskAsync();
await returnedTask;
// Single line
await GetTaskAsync();

Ogni attività restituita rappresenta il lavoro in corso. Un'attività incapsula informazioni sullo stato del processo asincrono e, eventualmente, il risultato finale del processo o l'eccezione che il processo solleva se non riesce.

Un metodo asincrono può anche avere un tipo restituito void. Questo tipo restituito viene usato principalmente per definire i gestori eventi, in cui è necessario un tipo restituito void. I gestori eventi asincroni spesso fungono da punto di partenza per i programmi asincroni.

Non è possibile attendere un metodo asincrono con un tipo restituito void e il chiamante di un metodo che restituisce void non può intercettare eccezioni generate dal metodo.

Un metodo asincrono non può dichiarare in, ref o parametri, ma il metodo può chiamare metodi con tali parametri. Analogamente, un metodo asincrono non può restituire un valore per riferimento, anche se può chiamare metodi con valori restituiti ref.

Per ulteriori informazioni ed esempi, vedere Tipi di ritorno asincroni (C#).

Le API asincrone nella programmazione di Windows Runtime hanno uno dei seguenti tipi di ritorno, che sono simili ai compiti:

Convenzione di denominazione

Per convenzione, i metodi che restituiscono tipi comunemente attendibili (ad esempio, Task, Task<T>, ValueTask, ValueTask<T>) devono avere nomi che terminano con "Async". I metodi che avviano un'operazione asincrona ma non restituiscono un tipo attendibile non devono avere nomi che terminano con "Async", ma possono iniziare con "Begin", "Start" o un altro verbo per suggerire che questo metodo non restituisce o lancia il risultato dell'operazione.

È possibile ignorare la convenzione in cui un evento, una classe di base o un contratto di interfaccia suggerisce un nome diverso. Ad esempio, non è consigliabile rinominare gestori eventi comuni, ad esempio OnButtonClick.

Articoli correlati (Visual Studio)

Titolo Descrizione
Come effettuare più richieste Web in parallelo usando async e await (C#) Illustra come avviare più attività contemporaneamente.
tipi restituiti asincroni (C#) Illustra i tipi che i metodi asincroni possono restituire e spiega quando ogni tipo è appropriato.
Annullare le attività con un token di annullamento come meccanismo di segnalazione. Illustra come aggiungere la funzionalità seguente alla soluzione asincrona:

- Annullare un elenco di attività (C#)
- Annullare le attività dopo un periodo di tempo (C#)
- 'attività asincrona Processo non appena vengono completate (C#)
Uso di asincrona per l'accesso ai file (C#) Elenca e illustra i vantaggi dell'uso di async e await per accedere ai file.
modello asincrono basato su attività (TAP) Descrive un modello asincrono, il modello si basa sui tipi Task e Task<TResult>.
Video asincroni su Channel 9 Fornisce collegamenti a un'ampia gamma di video sulla programmazione asincrona.

Vedere anche