Programmazione asincrona con async e await
Il modello di programmazione asincrona task (TAP) fornisce un livello di astrazione rispetto alla codifica asincrona tipica. In questo modello si scrive codice come sequenza di istruzioni, come di consueto. La differenza è che è possibile leggere il codice basato su attività mentre il compilatore elabora ogni istruzione e prima di iniziare a elaborare l'istruzione successiva. A tale scopo, il compilatore esegue molte trasformazioni per completare ogni attività. Alcune istruzioni possono avviare il lavoro e restituire un oggetto Task che rappresenta il lavoro in corso e il compilatore deve risolvere queste trasformazioni. L'obiettivo della programmazione asincrona delle attività è abilitare il codice che legge come una sequenza di istruzioni, ma viene eseguito in un ordine più complesso. L'esecuzione si basa sull'allocazione di risorse esterne e al termine delle attività.
Il modello di programmazione asincrona delle attività è analogo al modo in cui gli utenti forniscono istruzioni per i processi che includono attività asincrone. Questo articolo usa un esempio con istruzioni per fare colazione per mostrare come le parole chiave async
e await
rendono più semplice ragionare sul codice che include una serie di istruzioni asincrone. Le istruzioni per fare una colazione possono essere fornite come elenco:
- Versare una tazza di caffè.
- Scaldare una padella, quindi friggere due uova.
- Friggere tre fette di pancetta.
- Tostare due pezzi di pane.
- Spargere burro e marmellata sul tostato.
- Versare un bicchiere di succo d'arancia.
Se si ha esperienza con la cottura, è possibile completare queste istruzioni in modo asincrono. Si inizia a scaldare la padella per le uova, quindi iniziare a friggere la pancetta. Metti il pane nel tostapane, poi inizia a cucinare le uova. In ogni fase del processo, si inizia un'attività e poi si passa ad altre attività che sono pronte per essere affrontate.
Cucinare la colazione è un buon esempio di lavoro asincrono che non è parallelo. Una persona (o thread) può gestire tutte le attività. Una persona può fare colazione in modo asincrono avviando l'attività successiva prima del completamento dell'attività precedente. Ogni attività di cottura avanza indipendentemente dal fatto che qualcuno stia guardando attivamente il processo. Non appena inizi a scaldare la padella per le uova, puoi iniziare a friggere la pancetta. Dopo che la pancetta inizia a cucinare, è possibile mettere il pane nel tostapane.
Per un algoritmo parallelo, sono necessarie più persone che cucinano (o più thread). Una persona cucina le uova, un'altra friga la pancetta, e così via. Ogni persona si concentra sulla propria attività specifica. Ogni persona che sta cucinando (o ogni thread) viene bloccata sincronamente in attesa del completamento dell'attività corrente: Bacon pronto da girare, pane pronto a saltare fuori dal tostapane e così via.
Si consideri lo stesso elenco di istruzioni sincrone scritte come istruzioni di codice C#:
using System;
using System.Threading.Tasks;
namespace AsyncBreakfast
{
// These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
internal class Bacon { }
internal class Coffee { }
internal class Egg { }
internal class Juice { }
internal class Toast { }
class Program
{
static void Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
Egg eggs = FryEggs(2);
Console.WriteLine("eggs are ready");
Bacon bacon = FryBacon(3);
Console.WriteLine("bacon is ready");
Toast toast = ToastBread(2);
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
private static Juice PourOJ()
{
Console.WriteLine("Pouring orange juice");
return new Juice();
}
private static void ApplyJam(Toast toast) =>
Console.WriteLine("Putting jam on the toast");
private static void ApplyButter(Toast toast) =>
Console.WriteLine("Putting butter on the toast");
private static Toast ToastBread(int slices)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
Console.WriteLine("Start toasting...");
Task.Delay(3000).Wait();
Console.WriteLine("Remove toast from toaster");
return new Toast();
}
private static Bacon FryBacon(int slices)
{
Console.WriteLine($"putting {slices} slices of bacon in the pan");
Console.WriteLine("cooking first side of bacon...");
Task.Delay(3000).Wait();
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("flipping a slice of bacon");
}
Console.WriteLine("cooking the second side of bacon...");
Task.Delay(3000).Wait();
Console.WriteLine("Put bacon on plate");
return new Bacon();
}
private static Egg FryEggs(int howMany)
{
Console.WriteLine("Warming the egg pan...");
Task.Delay(3000).Wait();
Console.WriteLine($"cracking {howMany} eggs");
Console.WriteLine("cooking the eggs ...");
Task.Delay(3000).Wait();
Console.WriteLine("Put eggs on plate");
return new Egg();
}
private static Coffee PourCoffee()
{
Console.WriteLine("Pouring coffee");
return new Coffee();
}
}
}
Se si interpretano queste istruzioni come un computer, la colazione richiede circa 30 minuti per preparare. La durata è la somma dei singoli tempi di attività. Il computer si blocca per ogni istruzione fino al completamento di tutte le operazioni e quindi procede con l'istruzione dell'attività successiva. Questo approccio può richiedere tempo significativo. Nell'esempio di colazione il metodo computer crea una colazione insoddisfacente. Le attività successive nell'elenco sincrono, come tostare il pane, non iniziano finché non vengono completate quelle precedenti. Alcuni cibi si raffreddano prima che la colazione sia pronta da servire.
Per eseguire le istruzioni in modo asincrono, è necessario scrivere codice asincrono. Quando si scrivono programmi client, si vuole che l'interfaccia utente sia reattiva all'input dell'utente. L'applicazione non deve bloccare tutte le interazioni durante il download dei dati dal Web. Quando si scrivono programmi server, non si vogliono bloccare i thread che potrebbero gestire altre richieste. L'uso di codice sincrono quando esistono alternative asincrone fa male alla possibilità di aumentare le prestazioni in modo meno costoso. Si paga per i thread bloccati.
Le app moderne con esito positivo richiedono codice asincrono. Senza il supporto del linguaggio, la scrittura di codice asincrono richiede callback, eventi di completamento o altri mezzi che oscurano la finalità originale del codice. Il vantaggio del codice sincrono è l'azione dettagliata che semplifica l'analisi e la comprensione. I modelli asincroni tradizionali impongono di concentrarsi sulla natura asincrona del codice, non sulle azioni fondamentali del codice.
Non bloccare, attendere invece
Il codice precedente evidenzia una pratica di programmazione sfortunata: scrittura di codice sincrono per eseguire operazioni asincrone. Il codice impedisce al thread corrente di eseguire qualsiasi altra operazione. Il codice non interrompe il thread durante l'esecuzione di attività. Il risultato di questo modello è simile a fissare il tostapane dopo averci messo il pane. Ignori qualsiasi interruzione e non avvii altre attività fino a quando il pane non salta fuori. Non togliere il burro e la marmellata dal frigorifero. Potresti perdere la vista di un incendio che inizia sulla stufa. Si vuole tostare il pane e occuparsi contemporaneamente di altre faccende. Lo stesso vale per il codice.
È possibile iniziare aggiornando il codice in modo che il thread non blocchi durante l'esecuzione delle attività. La parola chiave await
fornisce un modo non bloccante per avviare un'attività, quindi continuare l'esecuzione al termine dell'attività. Una semplice versione asincrona del codice di colazione è simile al frammento seguente:
static async Task Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
Egg eggs = await FryEggsAsync(2);
Console.WriteLine("eggs are ready");
Bacon bacon = await FryBaconAsync(3);
Console.WriteLine("bacon is ready");
Toast toast = await ToastBreadAsync(2);
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
Il codice aggiorna i corpi del metodo di FryEggsAsync
, FryBaconAsync
e ToastBreadAsync
per restituire rispettivamente Task<Egg>
, Task<Bacon>
e Task<Toast>
oggetti . I nomi dei metodi includono il suffisso "Async". Il metodo Main
restituisce l'oggetto Task
, anche se non dispone di un'espressione return
, che è in base alla progettazione. Per ulteriori informazioni, vedere Valutazione di una funzione asincrona che restituisce void.
Nota
Il codice aggiornato non sfrutta ancora le funzionalità chiave della programmazione asincrona, che può comportare tempi di completamento più brevi. Il codice elabora le attività in circa la stessa quantità di tempo della versione sincrona iniziale. Per le implementazioni complete del metodo, vedere versione finale del codice più avanti in questo articolo.
Verrà ora applicato l'esempio di colazione al codice aggiornato. Il thread non si blocca mentre le uova o la pancetta sono in cottura, ma il codice non avvia anche altre attività fino al completamento del lavoro corrente. Metti ancora il pane nel tostapane e fissi il tostapane finché il pane non salta su, ma ora puoi rispondere alle interruzioni. In un ristorante in cui vengono effettuati più ordini, il cuoco può iniziare un nuovo ordine mentre un altro è già in preparazione.
Nel codice aggiornato, il thread che lavora sulla colazione non viene bloccato durante l'attesa di qualsiasi attività avviata non completata. Per alcune applicazioni, questa modifica è sufficiente. È possibile abilitare l'app per supportare l'interazione dell'utente durante il download dei dati dal Web. In altri scenari, potrebbe essere necessario avviare altre attività durante l'attesa del completamento dell'attività precedente.
Avviare le attività contemporaneamente
Per la maggior parte delle operazioni, si vogliono avviare immediatamente diverse attività indipendenti. Al completamento di ogni attività, inizi un altro lavoro che è pronto per partire. Quando si applica questa metodologia all'esempio di colazione, è possibile preparare la colazione più rapidamente. Si ottiene anche tutto pronto vicino allo stesso tempo, in modo da poter gustare una colazione calda.
La classe System.Threading.Tasks.Task e i tipi correlati sono classi che è possibile usare per applicare questo stile di ragionamento alle attività in corso. Questo approccio consente di scrivere codice più simile al modo in cui si crea la colazione nella vita reale. Si inizia a cucinare le uova, pancetta e toast allo stesso tempo. Poiché ogni elemento alimentare richiede un'azione, dirigi la tua attenzione a tale compito, ti occupi dell'azione e aspetti che qualcos'altro richieda la tua attenzione.
Nel codice si avvia un'attività e si mantiene l'oggetto Task che rappresenta l'attività. Utilizzare il metodo await
sull'attività per posticipare l'intervento sul lavoro fino a quando il risultato non è pronto.
Applicare queste modifiche al codice di colazione. Il primo passaggio consiste nell'archiviare le attività per le operazioni all'avvio, anziché usare l'espressione await
:
Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");
Task<Egg> eggsTask = FryEggsAsync(2);
Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Task<Bacon> baconTask = FryBaconAsync(3);
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");
Task<Toast> toastTask = ToastBreadAsync(2);
Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");
Console.WriteLine("Breakfast is ready!");
Queste revisioni non aiutano a preparare la colazione più velocemente. L'espressione await
viene applicata a tutte le attività non appena vengono avviate. Il passaggio successivo consiste nello spostare le espressioni await
per la pancetta e le uova alla fine del metodo, prima di servire la colazione:
Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");
Task<Egg> eggsTask = FryEggsAsync(2);
Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);
Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");
Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");
Console.WriteLine("Breakfast is ready!");
È ora disponibile una colazione preparata in modo asincrono che richiede circa 20 minuti per prepararsi. Il tempo totale di cottura viene ridotto perché alcune attività vengono eseguite simultaneamente.
Gli aggiornamenti del codice migliorano il processo di preparazione riducendo il tempo di cottura, ma introducono una regressione bruciando le uova e la pancetta. Tutte le attività asincrone vengono avviate contemporaneamente. Aspetti ogni attività solo quando ne hai bisogno dei risultati. Il codice potrebbe essere simile al programma in un'applicazione Web che effettua richieste a microservizi diversi e quindi combina i risultati in una singola pagina. Tutte le richieste vengono eseguite immediatamente e quindi si applica l'espressione await
in tutte le attività e si compone la pagina Web.
Supporto alla composizione delle attività
Le revisioni del codice precedenti consentono di preparare tutti gli elementi per la colazione contemporaneamente, ad eccezione del toast. Il processo di fare le fette di pane tostate è una composizione di un'operazione asincrona (tostare il pane) con operazioni sincrone (spalmare burro e marmellata sulla fetta di pane tostata). Questo esempio illustra un concetto importante di programmazione asincrona:
Importante
La composizione di un'operazione asincrona seguita da lavoro sincrono è un'operazione asincrona. Dichiarato in un altro modo, se una parte di un'operazione è asincrona, l'intera operazione è asincrona.
Negli aggiornamenti precedenti si è appreso come usare Task o Task<TResult> oggetti per contenere attività in esecuzione. Attendere il completamento di ogni attività prima di utilizzarne il risultato. Il passaggio successivo consiste nel creare metodi che rappresentano la combinazione di altre operazioni. Prima di servire la colazione, è necessario aspettare il compito di tostare il pane prima di spalmare il burro e la marmellata.
È possibile rappresentare questa operazione con il codice seguente:
static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
var toast = await ToastBreadAsync(number);
ApplyButter(toast);
ApplyJam(toast);
return toast;
}
Il metodo MakeToastWithButterAndJamAsync
ha il modificatore async
nella firma che segnala al compilatore che il metodo contiene un'espressione await
e contiene operazioni asincrone. Il metodo rappresenta l'attività che tosta il pane, quindi distribuisce il burro e la marmellata. Il metodo restituisce un oggetto Task<TResult> che rappresenta la composizione delle tre operazioni.
Il blocco principale di codice modificato è ora simile al seguente:
static async Task Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
var eggsTask = FryEggsAsync(2);
var baconTask = FryBaconAsync(3);
var toastTask = MakeToastWithButterAndJamAsync(2);
var eggs = await eggsTask;
Console.WriteLine("eggs are ready");
var bacon = await baconTask;
Console.WriteLine("bacon is ready");
var toast = await toastTask;
Console.WriteLine("toast is ready");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
Questa modifica al codice illustra una tecnica importante per l'uso del codice asincrono. È possibile comporre attività separando le operazioni in un nuovo metodo che restituisce un'attività. Puoi decidere quando attendere per quel compito. È possibile avviare contemporaneamente altre attività.
Gestire le eccezioni asincrone
Fino a questo punto, il codice presuppone implicitamente che tutte le attività siano state completate correttamente. I metodi asincroni generano eccezioni, proprio come le controparti sincrone. Gli obiettivi per il supporto asincrono per le eccezioni e la gestione degli errori sono gli stessi per il supporto asincrono in generale. La procedura consigliata consiste nel scrivere codice che legge come una serie di istruzioni sincrone. Le attività generano eccezioni quando non possono essere completate correttamente. Il codice client può intercettare tali eccezioni quando l'espressione await
viene applicata a un'attività avviata.
Nell'esempio di colazione, si supponga che il tostapane cattura fuoco mentre si tosta il pane. È possibile simulare il problema modificando il metodo ToastBreadAsync
in modo che corrisponda al codice seguente:
private static async Task<Toast> ToastBreadAsync(int slices)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
Console.WriteLine("Start toasting...");
await Task.Delay(2000);
Console.WriteLine("Fire! Toast is ruined!");
throw new InvalidOperationException("The toaster is on fire");
await Task.Delay(1000);
Console.WriteLine("Remove toast from toaster");
return new Toast();
}
Nota
Quando si compila questo codice, viene visualizzato un avviso relativo al codice non raggiungibile. Questo errore è intenzionale. Dopo l'incendio del tostapane, le operazioni non procedono normalmente e il codice restituisce un errore.
Dopo aver apportato le modifiche al codice, eseguire l'applicazione e controllare l'output:
Pouring coffee
Coffee is ready
Warming the egg pan...
putting 3 slices of bacon in the pan
Cooking first side of bacon...
Putting a slice of bread in the toaster
Putting a slice of bread in the toaster
Start toasting...
Fire! Toast is ruined!
Flipping a slice of bacon
Flipping a slice of bacon
Flipping a slice of bacon
Cooking the second side of bacon...
Cracking 2 eggs
Cooking the eggs ...
Put bacon on plate
Put eggs on plate
Eggs are ready
Bacon is ready
Unhandled exception. System.InvalidOperationException: The toaster is on fire
at AsyncBreakfast.Program.ToastBreadAsync(Int32 slices) in Program.cs:line 65
at AsyncBreakfast.Program.MakeToastWithButterAndJamAsync(Int32 number) in Program.cs:line 36
at AsyncBreakfast.Program.Main(String[] args) in Program.cs:line 24
at AsyncBreakfast.Program.<Main>(String[] args)
Si noti che parecchie attività terminano tra il momento in cui il tostapane prende fuoco e il sistema osserva l'eccezione. Quando un'attività che viene eseguita in modo asincrono genera un'eccezione, tale attività viene segnalata come errore. L'oggetto Task
contiene l'eccezione generata nella proprietà Task.Exception. Le attività con errori generano un'eccezione quando l'espressione await
viene applicata all'attività.
Esistono due meccanismi importanti per comprendere questo processo:
- Modalità di archiviazione di un'eccezione in un'attività con errori
- Come un'eccezione viene sconfezionata e rilanciata quando il codice attende (
await
) un'attività con guasto.
Quando il codice in esecuzione genera un'eccezione in modo asincrono, l'eccezione viene archiviata nell'oggetto Task
. La proprietà Task.Exception è un oggetto System.AggregateException perché durante il lavoro asincrono potrebbero essere generate più eccezioni. Qualsiasi eccezione generata viene aggiunta alla raccolta AggregateException.InnerExceptions. Se la proprietà Exception
è Null, viene creato un nuovo oggetto AggregateException
e l'eccezione generata è il primo elemento dell'insieme.
Lo scenario più comune per un'attività con errori è che la proprietà Exception
contiene esattamente un'eccezione. Quando il codice attende un'attività con errori, viene generata nuovamente la prima eccezione AggregateException.InnerExceptions nella raccolta. Questo risultato è il motivo per cui l'output dell'esempio mostra un oggetto System.InvalidOperationException anziché un oggetto AggregateException
. L'estrazione della prima eccezione interna rende il lavoro con i metodi asincroni il più simile possibile a quello con le loro controparti sincrone. È possibile esaminare la proprietà Exception
nel codice quando lo scenario potrebbe generare più eccezioni.
Suggerimento
La procedura consigliata consiste nel fare emergere eventuali eccezioni di convalida degli argomenti in modo sincrono dai metodi che restituiscono un'attività. Per altre informazioni ed esempi, vedere Eccezioni nei metodi che restituiscono attività.
Prima di continuare con la sezione successiva, impostare come commento le due istruzioni seguenti nel metodo ToastBreadAsync
. Non si vuole avviare un altro incendio:
Console.WriteLine("Fire! Toast is ruined!");
throw new InvalidOperationException("The toaster is on fire");
Applicare espressioni await alle attività in modo efficiente
È possibile migliorare la serie di espressioni await
alla fine del codice precedente usando i metodi della classe Task
. Un esempio di API è il metodo WhenAll, che restituisce un oggetto Task che si completa al termine di tutte le attività nell'elenco di argomenti. Il codice seguente illustra questo metodo:
await Task.WhenAll(eggsTask, baconTask, toastTask);
Console.WriteLine("Eggs are ready");
Console.WriteLine("Bacon is ready");
Console.WriteLine("Toast is ready");
Console.WriteLine("Breakfast is ready!");
Un'altra opzione consiste nell'utilizzare il metodo WhenAny, che restituisce un oggetto Task<Task>
che viene completato al termine di uno dei relativi argomenti. Puoi attendere l'attività restituita perché sai che è stata completata. Il codice seguente illustra come usare il metodo WhenAny per attendere il completamento della prima attività e quindi elaborarne il risultato. Dopo aver elaborato il risultato dall'attività completata, rimuovere l'attività completata dall'elenco di attività passate al metodo WhenAny
.
var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
while (breakfastTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(breakfastTasks);
if (finishedTask == eggsTask)
{
Console.WriteLine("Eggs are ready");
}
else if (finishedTask == baconTask)
{
Console.WriteLine("Bacon is ready");
}
else if (finishedTask == toastTask)
{
Console.WriteLine("Toast is ready");
}
await finishedTask;
breakfastTasks.Remove(finishedTask);
}
Nella parte finale del frammento di codice notare l'espressione await finishedTask;
. L'espressione await Task.WhenAny
non attende l'attività completata, ma attende piuttosto l'oggetto Task
restituito dal metodo Task.WhenAny
. Il risultato del metodo Task.WhenAny
è l'attività completata (o con errori). La procedura consigliata consiste nell'attendere di nuovo l'attività, anche quando si sa che l'attività è stata completata. In questo modo, è possibile recuperare il risultato dell'attività o assicurarsi che venga sollevata un'eccezione che causa il guasto dell'attività.
Esaminare il codice finale
Ecco l'aspetto della versione finale del codice:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace AsyncBreakfast
{
// These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
internal class Bacon { }
internal class Coffee { }
internal class Egg { }
internal class Juice { }
internal class Toast { }
class Program
{
static async Task Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
var eggsTask = FryEggsAsync(2);
var baconTask = FryBaconAsync(3);
var toastTask = MakeToastWithButterAndJamAsync(2);
var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
while (breakfastTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(breakfastTasks);
if (finishedTask == eggsTask)
{
Console.WriteLine("eggs are ready");
}
else if (finishedTask == baconTask)
{
Console.WriteLine("bacon is ready");
}
else if (finishedTask == toastTask)
{
Console.WriteLine("toast is ready");
}
await finishedTask;
breakfastTasks.Remove(finishedTask);
}
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
var toast = await ToastBreadAsync(number);
ApplyButter(toast);
ApplyJam(toast);
return toast;
}
private static Juice PourOJ()
{
Console.WriteLine("Pouring orange juice");
return new Juice();
}
private static void ApplyJam(Toast toast) =>
Console.WriteLine("Putting jam on the toast");
private static void ApplyButter(Toast toast) =>
Console.WriteLine("Putting butter on the toast");
private static async Task<Toast> ToastBreadAsync(int slices)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
Console.WriteLine("Start toasting...");
await Task.Delay(3000);
Console.WriteLine("Remove toast from toaster");
return new Toast();
}
private static async Task<Bacon> FryBaconAsync(int slices)
{
Console.WriteLine($"putting {slices} slices of bacon in the pan");
Console.WriteLine("cooking first side of bacon...");
await Task.Delay(3000);
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("flipping a slice of bacon");
}
Console.WriteLine("cooking the second side of bacon...");
await Task.Delay(3000);
Console.WriteLine("Put bacon on plate");
return new Bacon();
}
private static async Task<Egg> FryEggsAsync(int howMany)
{
Console.WriteLine("Warming the egg pan...");
await Task.Delay(3000);
Console.WriteLine($"cracking {howMany} eggs");
Console.WriteLine("cooking the eggs ...");
await Task.Delay(3000);
Console.WriteLine("Put eggs on plate");
return new Egg();
}
private static Coffee PourCoffee()
{
Console.WriteLine("Pouring coffee");
return new Coffee();
}
}
}
Il codice completa le attività di colazione asincrone in circa 15 minuti. Il tempo totale è ridotto perché alcune attività vengono eseguite simultaneamente. Il codice monitora simultaneamente più attività e esegue azioni solo in base alle esigenze.
Il codice finale è asincrono. Riflette in modo più accurato come una persona potrebbe cucinare la colazione. Confrontare il codice finale con il primo esempio di codice nell'articolo. Le azioni principali sono ancora chiare leggendo il codice. È possibile leggere il codice finale allo stesso modo in cui si legge l'elenco di istruzioni per fare una colazione, come illustrato all'inizio dell'articolo. Le funzionalità del linguaggio per le parole chiave async
e await
forniscono la traduzione che ogni persona effettua per seguire le istruzioni scritte: Iniziare le attività appena possibile e non bloccarsi mentre si aspetta che le attività siano completate.