Modello di programmazione asincrona Task in C#The Task asynchronous programming model in C#

Il modello di programmazione asincrona Task (TAP) offre un'astrazione su codice asincrono.The Task asynchronous programming model (TAP) provides an abstraction over asynchronous code. Scrivere il codice come sequenza di istruzioni secondo la normale procedura.You write code as a sequence of statements, just like always. È possibile leggere il codice come se ogni istruzione venisse completata prima che venga iniziata quella successiva.You can read that code as though each statement completes before the next begins. Il compilatore esegue una serie di trasformazioni poiché alcune delle istruzioni potrebbero essere eseguite e restituire Task che rappresenta il lavoro in corso.The compiler performs a number of transformations because some of those statements may start work and return a Task that represents the ongoing work.

L'obiettivo di questa sintassi consiste nell'abilitare un codice che viene letto come una sequenza di istruzioni ma viene eseguito in un ordine più complesso in base all'allocazione delle risorse esterne e al completamento dell'attività.That's the goal of this syntax: enable code that reads like a sequence of statements, but executes in a much more complicated order based on external resource allocation and when tasks complete. Si tratta di un funzionamento analogo a quello in cui gli utenti specificano istruzioni per i processi che includono attività asincrone.It's analogous to how people give instructions for processes that include asynchronous tasks. In questo articolo verrà usato un esempio di istruzioni per la preparazione di una colazione per osservare in che modo le parole chiave async e await consentono di motivare in modo più semplice un codice che include una serie di istruzioni asincrone.Throughout this article, you'll use an example of instructions for making a breakfast to see how the async and await keywords make it easier to reason about code that includes a series of asynchronous instructions. Si procederà a scrivere istruzioni come quelle dell'elenco seguente per descrivere come preparare una colazione:You'd write the instructions something like the following list to explain how to make a breakfast:

  1. Versare una tazza di caffè.Pour a cup of coffee.
  2. Scaldare una padella e friggere due uova.Heat up a pan, then fry two eggs.
  3. Friggere tre fette di pancetta.Fry three slices of bacon.
  4. Tostare due fette di pane.Toast two pieces of bread.
  5. Aggiungere burro e marmellata alla fetta di pane tostata.Add butter and jam to the toast.
  6. Versare un bicchiere di succo d'arancia.Pour a glass of orange juice.

Se si ha esperienza in cucina, queste istruzioni verranno eseguite in modo asincrono.If you have experience with cooking, you'd execute those instructions asynchronously. Si inizierà a scaldare la padella per le uova e si inizierà a cuocere la pancetta.You'd start warming the pan for eggs, then start the bacon. Si inserirà il pane nel tostapane, quindi si inizieranno a cuocere le uova.You'd put the bread in the toaster, then start the eggs. A ogni passaggio del processo si inizia un'attività, quindi ci si dedica alle attività che man mano richiedono attenzione.At each step of the process, you'd start a task, then turn your attention to tasks that are ready for your attention.

La preparazione della colazione è un buon esempio di lavoro asincrono non parallelo.Cooking breakfast is a good example of asynchronous work that isn't parallel. Tutte le attività possono essere gestite da una sola persona (o thread).One person (or thread) can handle all these tasks. Continuando con l'analogia della colazione, una sola persona può preparare la colazione in modo asincrono iniziando l'attività successiva prima che l'attività precedente venga completata.Continuing the breakfast analogy, one person can make breakfast asynchronously by starting the next task before the first completes. La preparazione procede indipendentemente dal fatto che venga controllata da qualcuno.The cooking progresses whether or not someone is watching it. Non appena si inizia a scaldare la padella per le uova, è possibile iniziare a friggere la pancetta.As soon as you start warming the pan for the eggs, you can begin frying the bacon. Dopo aver iniziato a cuocere la pancetta, è possibile inserire il pane nel tostapane.Once the bacon starts, you can put the bread into the toaster.

In un algoritmo parallelo sarebbero necessari più cuochi (o thread).For a parallel algorithm, you'd need multiple cooks (or threads). Un cuoco cucinerebbe le uova, un cuoco cucinerebbe la pancetta e così via.One would make the eggs, one the bacon, and so on. Ogni cuoco si dedicherebbe a una singola attività.Each one would be focused on just that one task. Ogni cuoco (o thread) verrebbe bloccato in modo sincrono in attesa che la pancetta sia pronta per essere girata o che la tostatura del pane venga completata.Each cook (or thread) would be blocked synchronously waiting for bacon to be ready to flip, or the toast to pop.

A questo punto, prendere in esame le stesse istruzioni scritte sotto forma di istruzioni C#:Now, consider those same instructions written as C# statements:

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!");
}

I computer non interpretano le istruzioni allo stesso modo delle persone.Computers don't interpret those instructions the same way people do. Il computer si bloccherà in corrispondenza di ogni istruzione fino a quando non verrà completata prima di passare all'istruzione successiva.The computer will block on each statement until the work is complete before moving on to the next statement. In questo modo non verrà preparata una colazione soddisfacente.That creates an unsatisfying breakfast. Le attività successive non verranno iniziate prima del completamento delle attività precedenti.The later tasks wouldn't be started until the earlier tasks had completed. La preparazione della colazione richiederà più tempo e alcuni alimenti si raffredderanno prima di essere serviti.It would take much longer to create the breakfast, and some items would have gotten cold before being served.

Se si vuole che il computer esegua le istruzioni precedenti in modo asincrono, è necessario scrivere codice asincrono.If you want the computer to execute the above instructions asynchronously, you must write asynchronous code.

Queste considerazioni sono importanti per l'attuale scrittura dei programmi.These concerns are important for the programs you write today. Quando si scrivono programmi client, si vuole che l'interfaccia utente risponda all'input dell'utente.When you write client programs, you want the UI to be responsive to user input. L'applicazione non deve bloccare l'uso del telefono durante il download di dati dal Web.Your application shouldn't make a phone appear frozen while it's downloading data from the web. Quando si scrivono programmi server, non si vuole che i thread vengano bloccati.When you write server programs, you don't want threads blocked. I thread potrebbero essere impegnati a rispondere ad altre richieste.Those threads could be serving other requests. L'uso di codice sincrono quando sono presenti alternative asincrone riduce la possibilità di aumentare le istanze in modo meno costoso.Using synchronous code when asynchronous alternatives exist hurts your ability to scale out less expensively. I thread bloccati hanno un costo.You pay for those blocked threads.

Per applicazioni moderne efficienti è necessario creare codice asincrono.Successful modern applications require asynchronous code. Senza supporto del linguaggio, la scrittura di codice asincrono richiedeva callback, eventi di completamento o altri elementi che nascondevano la finalità originale del codice.Without language support, writing asynchronous code required callbacks, completion events, or other means that obscured the original intent of the code. Il vantaggio del codice sincrono risiede nella semplicità di comprensione.The advantage of the synchronous code is that it's easy to understand. Le azioni passo passo rendono più semplice l'analisi e la comprensione.The step-by-step actions make it easy to scan and understand. Nei modelli asincroni tradizionali era necessario porre l'attenzione sulla natura asincrona del codice anziché sulle azioni fondamentali del codice.Traditional asynchronous models forced you to focus on the asynchronous nature of the code, not on the fundamental actions of the code.

Non bloccare, ma attendereDon't block, await instead

Il codice precedente illustra una prassi non corretta, ovvero la costruzione di codice sincrono per eseguire operazioni asincrone.The preceding code demonstrates a bad practice: constructing synchronous code to perform asynchronous operations. Come previsto, questo codice impedisce al thread che lo esegue di eseguire altre operazioni.As written, this code blocks the thread executing it from doing any other work. Non verrà interrotto mentre un'attività è in corso.It won't be interrupted while any of the tasks are in progress. Equivale a mettersi a osservare il tostapane dopo avere inserito il pane.It would be as though you stared at the toaster after putting the bread in. E a ignorare qualsiasi interlocutore fino a quando il pane non è pronto.You'd ignore anyone talking to you until the toast popped.

Si procederà ora ad aggiornare il codice in modo che il thread non venga bloccato mentre sono in esecuzione altre attività.Let's start by updating this code so that the thread doesn't block while tasks are running. La parola chiave await consente di iniziare un'attività senza alcun blocco e di continuare l'esecuzione al completamento dell'attività.The await keyword provides a non-blocking way to start a task, then continue execution when that task completes. Una versione asincrona semplice del codice della preparazione della colazione sarebbe simile al frammento seguente:A simple asynchronous version of the make a breakfast code would look like the following snippet:

static async Task Main(string[] args)
{
    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");
    Egg eggs = await FryEggs(2);
    Console.WriteLine("eggs are ready");
    Bacon bacon = await FryBacon(3);
    Console.WriteLine("bacon is ready");
    Toast toast = await ToastBread(2);
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");
    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");

    Console.WriteLine("Breakfast is ready!");
}

Questo codice non si blocca durante la cottura delle uova o della pancetta.This code doesn't block while the eggs or the bacon are cooking. Il codice tuttavia non inizia altre attività.This code won't start any other tasks though. Si inserisce il pane nel tostapane e si rimane a osservarlo fino al completamento della cottura.You'd still put the toast in the toaster and stare at it until it pops. Ma almeno si risponde a un interlocutore che richiede attenzione.But at least, you'd respond to anyone that wanted your attention. In un ristorante in cui vengono fatte più ordinazioni, il cuoco può iniziare a preparare un'altra colazione mentre la prima è in cottura.In a restaurant where multiple orders are placed, the cook could start another breakfast while the first is cooking.

Il thread impegnato nella preparazione della colazione non è bloccato in attesa che venga completata un'attività iniziata.Now, the thread working on the breakfast isn't blocked while awaiting any started task that hasn't yet finished. Per alcune applicazioni, questa modifica è tutto ciò che serve.For some applications, this change is all that's needed. Un'applicazione GUI risponde sempre all'utente solo con questa modifica.A GUI application still responds to the user with just this change. Tuttavia, per questo scenario si desidera un altro funzionamento.However, for this scenario, you want more. Non si vuole che ogni attività del componente venga eseguita in modo sequenziale.You don't want each of the component tasks to be executed sequentially. È preferibile iniziare ogni attività del componente prima del completamento dell'attività precedente.It's better to start each of the component tasks before awaiting the previous task's completion.

Iniziare più attività contemporaneamenteStart tasks concurrently

In molti scenari si vuole iniziare immediatamente più attività indipendenti.In many scenarios, you want to start several independent tasks immediately. Quindi, man mano che ogni attività viene terminata, è possibile passare ad altre operazioni da eseguire.Then, as each task finishes, you can continue other work that's ready. Nell'analogia della colazione, questa modalità consente di preparare la colazione più rapidamente.In the breakfast analogy, that's how you get breakfast done more quickly. Inoltre, tutte le operazioni vengono terminate quasi nello stesso momento.You also get everything done close to the same time. Si otterrà una colazione calda.You'll get a hot breakfast.

System.Threading.Tasks.Task e i tipi correlati sono classi che è possibile usare per motivare le attività in corso.The System.Threading.Tasks.Task and related types are classes you can use to reason about tasks that are in progress. in questo modo è possibile scrivere codice più simile al modo in cui effettivamente si prepara una colazione.That enables you to write code that more closely resembles the way you'd actually create breakfast. Si inizia a cuocere uova, pancetta e pane contemporaneamente.You'd start cooking the eggs, bacon, and toast at the same time. Man mano che ogni attività richiederà un'azione, si porrà l'attenzione su quell'attività, quindi sull'azione successiva e infine si rimarrà in attesa di altra attività da eseguire.As each requires action, you'd turn your attention to that task, take care of the next action, then await for something else that requires your attention.

Si inizia un'attività e la si mantiene nell'oggetto Task che rappresenta il lavoro.You start a task and hold on to the Task object that represents the work. Si rimarrà in attesa (await) di ogni attività prima di utilizzarne il risultato.You'll await each task before working with its result.

Verranno ora apportate queste modifiche al codice della colazione.Let's make these changes to the breakfast code. Il primo passaggio consiste nell'archiviare le attività delle operazioni quando vengono iniziate, anziché rimanere in attesa di esse:The first step is to store the tasks for operations when they start, rather than awaiting them:

Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
Task<Egg> eggTask = FryEggs(2);
Egg eggs = await eggTask;
Console.WriteLine("eggs are ready");
Task<Bacon> baconTask = FryBacon(3);
Bacon bacon = await baconTask;
Console.WriteLine("bacon is ready");
Task<Toast> toastTask = ToastBread(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!");

Successivamente, è possibile spostare le istruzioni await della pancetta e delle uova alla fine del metodo, prima di servire la colazione:Next, you can move the await statements for the bacon and eggs to the end of the method, before serving breakfast:

Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
Task<Egg> eggTask = FryEggs(2);
Task<Bacon> baconTask = FryBacon(3);
Task<Toast> toastTask = ToastBread(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 eggTask;
Console.WriteLine("eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("bacon is ready");

Console.WriteLine("Breakfast is ready!");

Il codice precedente ha un funzionamento migliore.The preceding code works better. Tutte le attività asincrone vengono iniziate contemporaneamente.You start all the asynchronous tasks at once. Si rimane in attesa di ogni attività solo quando è necessario avere a disposizione il risultato dell'attività.You await each task only when you need the results. Il codice precedente potrebbe essere simile al codice di un'applicazione Web che effettua le richieste di diversi microservizi, quindi unisce i risultati in una singola pagina.The preceding code may be similar to code in a web application that makes requests of different microservices, then combines the results into a single page. Si eseguiranno tutte le richieste immediatamente, quindi si rimarrà in attesa (await) di tutte le attività e si comporrà la pagina Web.You'll make all the requests immediately, then await all those tasks and compose the web page.

Composizione di attivitàComposition with tasks

Tutti gli alimenti della colazione sono pronti contemporaneamente ad eccezione del pane.You have everything ready for breakfast at the same time except the toast. La preparazione del pane rappresenta la composizione di un'operazione asincrona (tostatura del pane) e di operazioni sincrone (aggiunta del burro e della marmellata).Making the toast is the composition of an asynchronous operation (toasting the bread), and synchronous operations (adding the butter and the jam). L'aggiornamento di questo codice illustra un concetto importante:Updating this code illustrates an important concept:

Importante

La composizione di un'operazione asincrona, seguita da un lavoro sincrono è un'operazione asincrona.The composition of an asynchronous operation followed by synchronous work is an asynchronous operation. In altre parole, se una parte di un'operazione è asincrona, l'intera operazione è asincrona.Stated another way, if any portion of an operation is asynchronous, the entire operation is asynchronous.

Il codice precedente ha mostrato che è possibile usare gli oggetti Task o Task<TResult> per attività in esecuzione.The preceding code showed you that you can use Task or Task<TResult> objects to hold running tasks. Si rimane in attesa (await) di ogni attività prima di usarne il risultato.You await each task before using its result. Il passaggio successivo consiste nel creare metodi che rappresentano la combinazione di altre operazioni.The next step is to create methods that represent the combination of other work. Prima di servire la colazione, si vuole attendere l'attività che rappresenta la tostatura del pane prima dell'aggiunta del butto e della marmellata.Before serving breakfast, you want to await the task that represents toasting the bread before adding butter and jam. È possibile rappresentare queste operazioni con il codice seguente:You can represent that work with the following code:

async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
    var toast = await ToastBreadAsync(number);
    ApplyButter(toast);
    ApplyJam(toast);
    return toast;
}

Il metodo precedente include il modificatore async nella firma.The preceding method has the async modifier in its signature. Il modificatore segnala al compilatore che il metodo contiene un'istruzione await; contiene operazioni asincrone.That signals to the compiler that this method contains an await statement; it contains asynchronous operations. Questo metodo rappresenta l'attività di tostatura del pane, quindi aggiunge il burro e la marmellata.This method represents the task that toasts the bread, then adds butter and jam. Questo metodo restituisce Task<TResult> che rappresenta la composizione di queste tre operazioni.This method returns a Task<TResult> that represents the composition of those three operations. Il blocco di codice principale sarà ora il seguente:The main block of code now becomes:

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!");

    async Task<Toast> MakeToastWithButterAndJamAsync(int number)
    {
        var toast = await ToastBreadAsync(number);
        ApplyButter(toast);
        ApplyJam(toast);
        return toast;
    }
}

La modifica precedente ha illustrato una tecnica importante per l'uso di codice asincrono.The previous change illustrated an important technique for working with asynchronous code. Si compongono le attività separando le operazioni in un nuovo metodo che restituisce un'attività.You compose tasks by separating the operations into a new method that returns a task. È possibile scegliere quando rimanere in attesa dell'attività.You can choose when to await that task. È possibile iniziare altre attività contemporaneamente.You can start other tasks concurrently.

Attendere le attività in modo efficienteAwait tasks efficiently

La serie di istruzioni await alla fine del codice precedente può essere migliorata usando i metodi della classe Task.The series of await statements at the end of the preceding code can be improved by using methods of the Task class. Una delle API è WhenAll che restituisce Task che viene completata quando tutte le attività del relativo elenco di argomenti sono state completate, come illustrato nel codice seguente:One of those APIs is WhenAll, which returns a Task that completes when all the tasks in its argument list have completed, as shown in the following code:

await Task.WhenAll(eggTask, 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'usare WhenAny che restituisce Task<Task> che viene completata quando tutti i relativi argomenti vengono completati.Another option is to use WhenAny, which returns a Task<Task> that completes when any of its arguments completes. È possibile attendere l'attività restituita, sapendo che è già stata completata.You can await the returned task, knowing that it has already finished. Il codice seguente illustra come è possibile usare WhenAny per attendere il completamento della prima attività e quindi elaborarne il risultato.The following code shows how you could use WhenAny to await the first task to finish and then process its result. Dopo aver elaborato il risultato dell'attività completata, si rimuove l'attività completata dall'elenco delle attività passate a WhenAny.After processing the result from the completed task, you remove that completed task from the list of tasks passed to WhenAny.

var allTasks = new List<Task>{eggsTask, baconTask, toastTask};
while (allTasks.Any())
{
    Task finished = await Task.WhenAny(allTasks);
    if (finished == eggsTask)
    {
        Console.WriteLine("eggs are ready");
    }
    else if (finished == baconTask)
    {
        Console.WriteLine("bacon is ready");
    }
    else if (finished == toastTask)
    {
        Console.WriteLine("toast is ready");
    }
    allTasks.Remove(finished);
}
Console.WriteLine("Breakfast is ready!");

Dopo aver apportato tutte le modifiche, la versione finale di Main sarà simile al codice seguente:After all those changes, the final version of Main looks like the following code:

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 allTasks = new List<Task>{eggsTask, baconTask, toastTask};
    while (allTasks.Any())
    {
        Task finished = await Task.WhenAny(allTasks);
        if (finished == eggsTask)
        {
            Console.WriteLine("eggs are ready");
        }
        else if (finished == baconTask)
        {
            Console.WriteLine("bacon is ready");
        }
        else if (finished == toastTask)
        {
            Console.WriteLine("toast is ready");
        }
        allTasks.Remove(finished);
    }
    Console.WriteLine("Breakfast is ready!");

    async Task<Toast> MakeToastWithButterAndJamAsync(int number)
    {
        var toast = await ToastBreadAsync(number);
        ApplyButter(toast);
        ApplyJam(toast);
        return toast;
    }
}

Il codice finale è asincrono.This final code is asynchronous. Riflette con maggior precisione il modo in cui viene preparata una colazione.It more accurately reflects how a person would cook a breakfast. Confrontare il codice precedente con il primo esempio di codice di questo articolo.Compare the preceding code with the first code sample in this article. Le azioni principali risultano ancora chiare dalla lettura del codice.The core actions are still clear from reading the code. È possibile leggere il codice allo stesso modo in cui si leggerebbero le istruzioni per preparare una colazione riportate all'inizio di questo articolo.You can read this code the same way you'd read those instructions for making a breakfast at the beginning of this article. Le funzionalità del linguaggio per async e await offrono la traduzione che ogni persona farebbe per seguire le istruzioni scritte: iniziare le attività non appena possibile e non bloccarsi in attesa del completamento delle attività.The language features for async and await provide the translation every person makes to follow those written instructions: start tasks as you can and don't block waiting for tasks to complete.