Modello di programmazione asincrona attività

È possibile evitare colli di bottiglia nelle prestazioni e migliorare la risposta generale dell'applicazione utilizzando la programmazione asincrona. Le tecniche tradizionali per la scrittura di applicazioni asincrone, tuttavia, possono essere complesse, rendendone difficile la scrittura, il debug e la gestione.

C# supporta un approccio semplificato, ovvero la programmazione asincrona, che sfrutta il supporto asincrono nel runtime di .NET. Il compilatore esegue il lavoro difficile che prima veniva svolto dallo sviluppatore e l'applicazione mantiene una struttura logica simile al codice sincrono. Di conseguenza, si ottengono tutti i vantaggi della programmazione asincrona con meno lavoro richiesto.

In questo argomento viene fornita una panoramica di come e quando utilizzare la programmazione asincrona e vengono forniti collegamenti per supportare gli argomenti contenenti informazioni dettagliate ed esempi.

L'approccio asincrono migliora la velocità di risposta

La modalità asincrona è essenziale per le attività che potenzialmente bloccano l'esecuzione, ad esempio l'accesso al Web. L'accesso a una risorsa Web può essere talvolta lento o ritardato. Se questa attività viene bloccata in un processo sincrono, l'intera applicazione deve attendere. In un processo asincrono l'applicazione può invece continuare con un altro lavoro che non dipende dalla risorsa Web finché l'attività di blocco non termina.

Nella tabella seguente sono mostrate le aree tipiche in cui la programmazione asincrona migliora la 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 Windows Runtime con metodi asincroni
Accesso Web HttpClient Windows.Web.Http.HttpClient
SyndicationClient
Usare i file JsonSerializer
StreamReader
StreamWriter
XmlReader
XmlWriter
StorageFile
Utilizzo di immagini MediaCapture
BitmapEncoder
BitmapDecoder
Programmazione WCF Operazioni sincrone e asincrone

La modalità asincrona è particolarmente importante per le applicazioni che accedono al thread dell'interfaccia utente poiché tutte le attività correlate all'interfaccia utente in genere condividono un thread. Se un processo è bloccato in un'applicazione sincrona, tutte le attività saranno bloccate. L'applicazione non risponde e si potrebbe pensare che si sia verificato un errore mentre si tratta solo di un'applicazione attesa.

Quando si utilizzano i metodi asincroni, l'applicazione continua a rispondere all'interfaccia utente. È possibile ad esempio ridimensionare o ridurre a icona una finestra oppure è possibile chiudere l'applicazione se non si desidera attendere il completamento.

L'approccio basato su modalità asincrona aggiunge l'equivalente di una trasmissione automatica all'elenco di opzioni da cui è possibile scegliere quando si progettano operazioni asincrone. In questo modo si ottengono tutti i vantaggi della programmazione asincrona tradizionale con meno lavoro richiesto allo sviluppatore.

I metodi asincroni sono più semplici da scrivere

Le parole chiave async e await in C# sono il punto centrale della programmazione asincrona. Tramite queste due parole chiave, è possibile usare le risorse di .NET Framework, .NET Core o Windows Runtime per creare un metodo asincrono con la stessa facilità con cui è possibile creare un metodo sincrono. I metodi asincroni definiti con la parola chiave async sono denominati metodi asincroni.

Nell'esempio seguente viene illustrato un metodo asincrono. Quasi tutti gli elementi del codice dovrebbero già essere noti all'utente.

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

public async Task<int> GetUrlContentLengthAsync()
{
    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 dedurre diverse cose. Partiamo, ad esempio, dalla firma del metodo che include il modificatore async. Il tipo restituito è Task<int> (Per altre opzioni vedere la sezione "Tipi restituiti e parametri"). Il nome del metodo finisce con Async. Nel corpo del metodo GetStringAsync restituisce un oggetto Task<string>. Ciò significa che quando si esegue await sull'attività si ottiene un oggetto string (contents). Prima di mettere in attesa l'attività, è possibile eseguire operazioni che non si basano sull'oggetto string da GetStringAsync.

Prestare attenzione all'operatore await Sospende GetUrlContentLengthAsync:

  • GetUrlContentLengthAsync non può continuare finché l'operazione getStringTask non è stata completata.
  • Nel frattempo il controllo viene restituito al chiamante di GetUrlContentLengthAsync.
  • Il controllo riprende qui quando l'operazione getStringTask è stata completata.
  • In seguito l'operatore await recupera il risultato di string da getStringTask.

L'istruzione return specifica un risultato intero. Tutti i metodi che mettono GetUrlContentLengthAsync in attesa recuperano il valore della lunghezza.

Se GetUrlContentLengthAsync non ha alcuna operazione da eseguire tra la chiamata di GetStringAsync e il relativo completamento, è possibile semplificare il codice chiamando l'istruzione singola seguente e rimanendo in attesa.

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

Le seguenti caratteristiche riepilogano gli aspetti che identificano l'esempio precedente come un metodo asincrono:

  • La firma del metodo include un modificatore async.

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

  • Il tipo di valore restituito è uno dei seguenti:

    • Task<TResult> se nel metodo è presente un'istruzione return in cui l'operando è di tipo TResult.
    • Task se nel metodo non è presente un'istruzione return oppure è presente un'istruzione return senza l'operando.
    • void se si sta scrivendo un gestore eventi asincrono.
    • Qualsiasi altro tipo con un metodo GetAwaiter.

    Per altre informazioni, vedere la sezione Tipi restituiti e parametri.

  • Il metodo include in genere almeno un'espressione await, che contrassegna un punto in cui il metodo non può continuare fino a quando l'operazione asincrona in attesa non è stata completata. Nel frattempo, il metodo viene sospeso e il controllo ritorna al chiamante del metodo. Nella sezione successiva di questo argomento viene illustrato quello che accade in corrispondenza del punto di sospensione.

Nei metodi asincroni utilizzare le parole chiave e i tipi forniti per indicare l'operazione da eseguire e il compilatore esegue il resto dell'operazione, inclusa la traccia di cosa deve verificarsi quando il controllo viene restituito a un punto di attesa in un metodo sospeso. Alcuni processi di routine, come cicli e gestione delle eccezioni, possono essere difficili da gestire nel codice asincrono tradizionale. In un metodo asincrono scrivere questi elementi come in una soluzione sincrona e il problema viene risolto.

Per altre informazioni sulla modalità asincrona in versioni precedenti di .NET Framework, vedere TPL e programmazione asincrona .NET Framework tradizionale.

Operazioni eseguite in un metodo asincrono

La cosa più importante da capire nella programmazione asincrona è il modo in cui il flusso del controllo si sposta da un metodo all'altro. Nel diagramma seguente viene descritto il processo:

Trace navigation of async control flow

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

  1. Un metodo chiamante chiama 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. Si verifica un evento in GetStringAsync che ne sospende lo stato di avanzamento forse perché deve attendere il termine dello scaricamento 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 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 effettivo a completamento del lavoro.

  4. Poiché getStringTask non è stata ancora attesa, GetUrlContentLengthAsync può continuare con altro lavoro che non dipende dal risultato finale ottenuto da GetStringAsync. Tale lavoro è rappresentato da una chiamata al metodo sincrono DoIndependentWork.

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

  6. GetUrlContentLengthAsync ha esaurito il lavoro che può eseguire senza un risultato da getStringTask. GetUrlContentLengthAsync deve quindi calcolare e restituire la lunghezza della stringa scaricata, ma il metodo non può calcolare il valore finché quest'ultimo non contiene la stringa.

    Di conseguenza, GetUrlContentLengthAsync utilizza un operatore await per sospendere lo stato di avanzamento e restituire il controllo al metodo che ha chiamato GetUrlContentLengthAsync. GetUrlContentLengthAsync restituisce Task<int> al chiamante. L'attività rappresenta l'intenzione di produrre un risultato di tipo Integer che è la lunghezza della stringa scaricata.

    Nota

    Se l'operazione GetStringAsync (e quindi getStringTask) viene completata prima che GetUrlContentLengthAsync la metta in attesa, il controllo resta a GetUrlContentLengthAsync. I costi per sospendere e tornare a GetUrlContentLengthAsync sarebbero sprecati se il processo asincrono chiamato getStringTask fosse già completato e GetUrlContentLengthAsync non dovesse attendere il risultato finale.

    All'interno del metodo chiamante il modello di elaborazione continua. Il chiamante può eseguire altre attività che non dipendono dal risultato di GetUrlContentLengthAsync prima di attendere tale risultato oppure può mettersi immediatamente in attesa. Il metodo chiamante è in attesa di GetUrlContentLengthAsync e GetUrlContentLengthAsync è in attesa di GetStringAsync.

  7. GetStringAsync termina e produce un risultato di stringa. Il risultato di stringa non viene restituito dalla chiamata a GetStringAsync nel modo previsto. Tenere presente che il metodo non ha restituito un'attività al passaggio 3. Il risultato di stringa viene invece memorizzato nell'attività che rappresenta il completamento del metodo, ovvero 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 di stringa, il metodo può calcolare la lunghezza della stringa. Il lavoro di GetUrlContentLengthAsync è quindi completo e il gestore eventi in attesa può riprendere l'attività. Nell'esempio completo alla fine dell'argomento è possibile confermare che il gestore eventi recupera e stampa il valore del risultato di lunghezza. Se non si ha familiarità con la programmazione asincrona, valutare la differenza tra il comportamento sincrono e asincrono. Viene restituito un metodo sincrono quando il lavoro è completato (passaggio 5), ma un metodo asincrono restituisce un valore di attività quando il relativo lavoro viene sospeso (passaggi 3 e 6). Una volta che il metodo asincrono completa l'operazione, l'attività viene contrassegnata come completata e il risultato, se disponibile, viene archiviato nell'attività.

Metodi asincroni per API

Metodi come GetStringAsync che supportano la programmazione asincrona In .NET Framework 4.5 o versioni successive e in .NET Core sono presenti diversi membri che usano 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, ReadAsync e WriteAsync insieme ai metodi sincroni CopyTo, Read e Write.

Windows Runtime contiene inoltre molti metodi che è possibile usare con async e await in app Windows. Per altre informazioni, vedere Threading e programmazione asincrona per lo sviluppo UWP e Programmazione asincrona (app Windows Store) e Quickstart: Calling asynchronous APIs in C# or Visual Basic (Guida introduttiva: Chiamata di API asincrone in C# o Visual Basic) se si usano versioni precedenti di Windows Runtime.

Threads

I metodi asincroni vengono considerati operazioni non bloccanti. Un'espressione await in un metodo asincrono non blocca il thread corrente quando l'attività attesa è in esecuzione. Al contrario, 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 determinano la creazione di thread aggiuntivi. I metodi asincroni non richiedono multithreading perché un metodo asincrono non viene eseguito nel proprio thread. Il metodo viene eseguito nel contesto di sincronizzazione corrente e utilizza il tempo sul thread solo se il metodo è attivo. È possibile utilizzare Task.Run per spostare un lavoro associato alla CPU in un thread in background. Quest'ultimo tuttavia non è di alcun ausilio in un processo che attende solo che i risultati diventino disponibili.

L'approccio alla programmazione asincrona basato su async è quasi sempre preferibile agli approcci esistenti. In particolare, questo approccio è preferibile alla classe BackgroundWorker per le operazioni di I/O poiché il codice è più semplice e non è necessario proteggersi da situazioni di race condition. Insieme al metodo Task.Run, la programmazione asincrona è migliore di BackgroundWorker per le operazioni associate alla CPU perché separa i dettagli di coordinamento per l'esecuzione del codice dal lavoro che Task.Run trasferisce al pool di thread.

async e await

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

  • Il metodo asincrono contrassegnato può usare await per definire i punti di sospensione. L'operatore await indica al compilatore che il metodo asincrono non può continuare oltre un dato punto prima del completamento del processo asincrono in attesa. Nel frattempo il controllo viene restituito al chiamante del metodo asincrono.

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

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

Un metodo asincrono contiene in genere una o più occorrenze di un operatore await, mentre l'assenza di espressioni await non determina un errore del compilatore. Se un metodo asincrono non usa un operatore await per contrassegnare un punto di sospensione, si comporterà come un metodo sincrono nonostante la presenza del modificatore async. Il compilatore genera un avviso per tali metodi.

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

Tipi restituiti e parametri

Un metodo asincrono restituisce in genere Task o Task<TResult>. In 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.

Utilizzare Task come tipo restituito se il metodo non include un'istruzione return o contiene un'istruzione return che non restituisce un operando.

È possibile specificare anche altri tipi restituiti, a condizione che il tipo includa un metodo GetAwaiter. Un esempio dei tipi in questione è ValueTask<TResult>. disponibile nel pacchetto NuGet System.Threading.Tasks.Extension.

Di seguito viene illustrato come dichiarare e chiamare un metodo che restituisce Task<TResult> o 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 attualmente in fase di esecuzione. Un'attività include le informazioni sullo stato del processo asincrono e, infine, il risultato finale del processo o l'eccezione che il processo genera se non viene completato.

Un metodo asincrono può avere un tipo restituito void. Il tipo restituito viene utilizzato principalmente per definire i gestori eventi, dove un tipo restituito void è necessario. I gestori eventi asincroni fungono spesso da punto di partenza per i programmi asincroni.

Un metodo asincrono con un tipo restituito void non può essere atteso e il chiamante di un metodo che restituisce un valore void non può rilevare eventuali eccezioni generate dal metodo.

Un metodo asincrono non può dichiarare parametri in, ref o out, ma può chiamare metodi con tali parametri. Analogamente, un metodo asincrono non può restituire un valore tramite riferimento, sebbene possa chiamare metodi con valori restituiti di riferimento.

Per altre informazioni ed esempi, vedere Tipi restituiti asincroni (C#).

Nella programmazione Windows Runtime le API asincrone hanno uno dei tipi restituiti seguenti, che sono simili alle attività:

Convenzione di denominazione

Per convenzione, i metodi che restituiscono tipi comunemente awaitable (come Task, Task<T>, ValueTask, ValueTask<T>) dovrebbero avere nomi che terminano con "Async". I metodi che avviano un'operazione asincrona, ma non restituiscono un tipo awaitable, non devono avere nomi che terminano con "Async", ma possono iniziare con "Begin", "Start" o altri verbi per suggerire che questo metodo non restituisce o genera il risultato dell'operazione.

È possibile ignorare la convenzione se un evento, una classe base o un contratto di interfaccia suggerisce un nome diverso. Ad esempio, non è necessario rinominare i gestori eventi comuni, come OnButtonClick.

Articoli correlati (Visual Studio)

Posizione Descrizione
Procedura: Eseguire più richieste Web in parallelo tramite async e await (C#) Viene illustrato come avviare contemporaneamente diverse attività.
Tipi restituiti asincroni (C#) Vengono illustrati i tipi che i metodi asincroni possono restituire e viene spiegato quando ogni tipo è appropriato.
Annullare le attività con un token di annullamento come meccanismo di segnalazione. Mostra come aggiungere la seguente funzionalità alla soluzione asincrono:

- Annullare un elenco di attività (C#)
- Annullare attività dopo un periodo di tempo (C#)
- Elaborare le attività asincrone non appena vengono completate (C#)
Utilizzo della funzionalità Async per l'accesso ai file (C#) Vengono elencati e illustrati i vantaggi dell'utilizzo di async e await per accedere ai file.
Modello asincrono basato su attività (TAP) Descrive un modello asincrono, basato sui tipi Task e Task<TResult>.
Video sulla modalità asincrona su Channel 9 Vengono forniti collegamenti a una serie di video sulla programmazione asincrona.

Vedi anche