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 delstring
dagetStringTask
.
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.
-
Task<TResult> se il metodo ha un'istruzione di return in cui l'operando ha tipo
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:
I numeri nel diagramma corrispondono ai passaggi seguenti, avviati quando un metodo chiamante chiama il metodo asincrono.
Un metodo chiamante invoca 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.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>, 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 reale al termine del lavoro.Poiché
getStringTask
non è stato ancora atteso,GetUrlContentLengthAsync
può continuare con altri lavori che non dipendono dal risultato finale diGetStringAsync
. Tale lavoro è rappresentato da una chiamata al metodo sincronoDoIndependentWork
.DoIndependentWork
è un metodo sincrono che esegue il lavoro e restituisce al chiamante.GetUrlContentLengthAsync
ha esaurito il lavoro che può eseguire senza un risultato dagetStringTask
.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 chiamatoGetUrlContentLengthAsync
.GetUrlContentLengthAsync
restituisce unTask<int>
al chiamante. L'attività rappresenta una promessa per produrre un risultato integer che corrisponde alla lunghezza della stringa scaricata.Nota
Se
GetStringAsync
(e quindigetStringTask
) viene completato prima cheGetUrlContentLengthAsync
lo attenda, il controllo rimane inGetUrlContentLengthAsync
. La spesa per la sospensione e quindi il ritorno aGetUrlContentLengthAsync
sarebbe sprecato se il processo asincrono chiamatogetStringTask
è già stato completato eGetUrlContentLengthAsync
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 diGetUrlContentLengthAsync
eGetUrlContentLengthAsync
è in attesa diGetStringAsync
.GetStringAsync
completa e produce un risultato sotto forma di stringa. Il risultato della stringa non viene restituito dalla chiamata aGetStringAsync
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 metodogetStringTask
. L'operatore await recupera il risultato dagetStringTask
. L'istruzione di assegnazione assegna il risultato recuperato acontents
.Quando
GetUrlContentLengthAsync
ha il risultato della stringa, il metodo può calcolare la lunghezza della stringa. Il lavoro diGetUrlContentLengthAsync
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 blocchifinally
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:
- asincrona
- attendi
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:
- 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 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. |