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()
{
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 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'operazionegetStringTask
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 distring
dagetStringTask
.
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.
- Task<TResult> se nel metodo è presente un'istruzione return in cui l'operando è di tipo
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:
I numeri nel diagramma corrispondono ai passaggi seguenti, avviati quando un metodo chiamante chiama il metodo asincrono.
Un metodo chiamante chiama e attende il metodo asincrono
GetUrlContentLengthAsync
.GetUrlContentLengthAsync
crea un'istanza di HttpClient e chiama il metodo asincrono GetStringAsync per scaricare il contenuto di un sito Web come stringa.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 chiamanteGetUrlContentLengthAsync
.GetStringAsync
restituisce Task<TResult>, doveTResult
è una stringa eGetUrlContentLengthAsync
assegna l'attività alla variabilegetStringTask
. L'attività rappresenta il processo in corso per la chiamata aGetStringAsync
, con l'impegno di produrre un valore stringa effettivo a completamento del lavoro.Poiché
getStringTask
non è stata ancora attesa,GetUrlContentLengthAsync
può continuare con altro lavoro che non dipende dal risultato finale ottenuto daGetStringAsync
. Tale lavoro è rappresentato da una chiamata al metodo sincronoDoIndependentWork
.DoIndependentWork
è un metodo sincrono che esegue il proprio lavoro e lo restituisce al chiamante.GetUrlContentLengthAsync
ha esaurito il lavoro che può eseguire senza un risultato dagetStringTask
.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 chiamatoGetUrlContentLengthAsync
.GetUrlContentLengthAsync
restituisceTask<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 quindigetStringTask
) viene completata prima cheGetUrlContentLengthAsync
la metta in attesa, il controllo resta aGetUrlContentLengthAsync
. I costi per sospendere e tornare aGetUrlContentLengthAsync
sarebbero sprecati se il processo asincrono chiamatogetStringTask
fosse già completato eGetUrlContentLengthAsync
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 diGetUrlContentLengthAsync
eGetUrlContentLengthAsync
è in attesa diGetStringAsync
.GetStringAsync
termina e produce un risultato di stringa. Il risultato di stringa non viene restituito dalla chiamata aGetStringAsync
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, ovverogetStringTask
. L'operatore await recupera il risultato dagetStringTask
. L'istruzione di assegnazione assegna il risultato recuperato acontents
.Quando
GetUrlContentLengthAsync
ha il risultato di stringa, il metodo può calcolare la lunghezza della stringa. Il lavoro diGetUrlContentLengthAsync
è 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 blocchifinally
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à:
- IAsyncOperation<TResult>, che corrisponde a Task<TResult>
- IAsyncAction, che corrisponde a Task
- IAsyncActionWithProgress<TProgress>
- IAsyncOperationWithProgress<TResult,TProgress>
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. |