O modelo de programação assíncrono Task em C#The Task asynchronous programming model in C#

O modelo TAP (programação assíncrona Task) proporciona uma abstração em código assíncrono.The Task asynchronous programming model (TAP) provides an abstraction over asynchronous code. Você escreve o código como uma sequência de instruções, como usual.You write code as a sequence of statements, just like always. Você pode ler o código como se cada instrução fosse concluída antes do início da próxima.You can read that code as though each statement completes before the next begins. O compilador realiza uma série de transformações, porque algumas dessas instruções podem iniciar o trabalho e retornar um Task que representa o trabalho em andamento.The compiler performs a number of transformations because some of those statements may start work and return a Task that represents the ongoing work.

Essa é a meta dessa sintaxe: habilitar um código que leia como uma sequência de instruções, mas que execute em uma ordem muito mais complicada com base na alocação de recurso externo e em quando as tarefas são concluídas.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. Isso é semelhante à maneira como as pessoas dão instruções para processos que incluem tarefas assíncronas.It's analogous to how people give instructions for processes that include asynchronous tasks. Neste artigo, você usará um exemplo com instruções para fazer um café da manhã e ver como as palavras-chave async e await facilitam raciocinar sobre o código que inclui uma série de instruções assíncronas.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. Você deve escrever as instruções de maneira parecida com a lista a seguir para explicar como fazer um café da manhã:You'd write the instructions something like the following list to explain how to make a breakfast:

  1. Encher uma xícara de café.Pour a cup of coffee.
  2. Aquecer uma frigideira e, em seguida, fritar dois ovos.Heat up a pan, then fry two eggs.
  3. Frita três fatias de bacon.Fry three slices of bacon.
  4. Torrar dois pedaços de pão.Toast two pieces of bread.
  5. Adicionar manteiga e a geleia na torrada.Add butter and jam to the toast.
  6. Encher um copo com suco de laranja.Pour a glass of orange juice.

Se tivesse experiência em culinária, você executaria essas instruções assincronamente.If you have experience with cooking, you'd execute those instructions asynchronously. Você iniciaria aquecendo a frigideira para os ovos e, em seguida, começaria a preparar o bacon.You'd start warming the pan for eggs, then start the bacon. Você colocaria o pão na torradeira e começaria a preparar os ovos.You'd put the bread in the toaster, then start the eggs. Em cada etapa do processo, iniciaria uma tarefa e voltaria sua atenção para as tarefas que estivessem prontas para a sua atenção.At each step of the process, you'd start a task, then turn your attention to tasks that are ready for your attention.

Preparar o café da manhã é um bom exemplo de trabalho assíncrono que não é paralelo.Cooking breakfast is a good example of asynchronous work that isn't parallel. Uma pessoa (ou um thread) pode lidar com todas essas tarefas.One person (or thread) can handle all these tasks. Continuando com a analogia do café da manhã, uma pessoa pode fazer café da manhã assincronamente iniciando a tarefa seguinte antes de concluir a primeira.Continuing the breakfast analogy, one person can make breakfast asynchronously by starting the next task before the first completes. O preparo progride independentemente de haver alguém observando.The cooking progresses whether or not someone is watching it. Assim que inicia o aquecimento da frigideira para os ovos, você pode começar a fritar o bacon.As soon as you start warming the pan for the eggs, you can begin frying the bacon. Quando começar a preparar o bacon, você pode colocar o pão na torradeira.Once the bacon starts, you can put the bread into the toaster.

Para um algoritmo paralelo, você precisaria de vários cozinheiros (ou threads).For a parallel algorithm, you'd need multiple cooks (or threads). Um prepararia os ovos, outro o bacon e assim por diante.One would make the eggs, one the bacon, and so on. Cada um se concentraria apenas naquela tarefa específica.Each one would be focused on just that one task. Cada cozinheiro (ou thread) ficaria bloqueado de forma síncrona, esperando que o bacon estivesse pronto para ser virado ou que a torrada pulasse.Each cook (or thread) would be blocked synchronously waiting for bacon to be ready to flip, or the toast to pop.

Agora, considere essas mesmas instruções escritas como instruções em 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!");
}

Os computadores não interpretam essas instruções da mesma forma que as pessoas.Computers don't interpret those instructions the same way people do. O computador ficará bloqueado em cada instrução até que o trabalho seja concluído, antes de passar para a próxima instrução.The computer will block on each statement until the work is complete before moving on to the next statement. Isso cria um café da manhã insatisfatório.That creates an unsatisfying breakfast. As tarefas posteriores não seriam iniciadas até que as tarefas anteriores fossem concluídas.The later tasks wouldn't be started until the earlier tasks had completed. Levaria muito mais tempo para criar o café da manhã e alguns itens ficariam frios antes de serem servidos.It would take much longer to create the breakfast, and some items would have gotten cold before being served.

Se você quiser que o computador execute as instruções acima de forma assíncrona, deverá escrever o código assíncrono.If you want the computer to execute the above instructions asynchronously, you must write asynchronous code.

Essas questões são importantes para os programas que você escreve atualmente.These concerns are important for the programs you write today. Ao escrever programas de cliente, você quer que a interface do usuário responda de acordo com as solicitações do usuário.When you write client programs, you want the UI to be responsive to user input. Seu aplicativo não deve fazer um telefone parecer travado enquanto ele está baixando dados da Web.Your application shouldn't make a phone appear frozen while it's downloading data from the web. Ao escrever programas de servidor, você não quer threads bloqueados.When you write server programs, you don't want threads blocked. Esses threads poderiam servir a outras solicitações.Those threads could be serving other requests. O uso de código síncrono quando existem alternativas assíncronas afeta sua capacidade de aumentar de forma menos custosa.Using synchronous code when asynchronous alternatives exist hurts your ability to scale out less expensively. Você paga pelos threads bloqueados.You pay for those blocked threads.

Aplicativos modernos bem-sucedidos exigem código assíncrono.Successful modern applications require asynchronous code. Sem suporte de linguagem, escrever código assíncrono exigia retornos de chamada, eventos de conclusão ou outros meios que obscureciam a intenção original do código.Without language support, writing asynchronous code required callbacks, completion events, or other means that obscured the original intent of the code. A vantagem do código síncrono é que ele é fácil de entender.The advantage of the synchronous code is that it's easy to understand. As ações passo a passo facilitam o exame e o entendimento.The step-by-step actions make it easy to scan and understand. Modelos assíncronos tradicionais forçavam você a se concentrar na natureza assíncrona do código e não nas ações fundamentais do código.Traditional asynchronous models forced you to focus on the asynchronous nature of the code, not on the fundamental actions of the code.

Não bloquear, mas aguardarDon't block, await instead

O código anterior demonstra uma prática inadequada: construção de código síncrono para realizar operações assíncronas.The preceding code demonstrates a bad practice: constructing synchronous code to perform asynchronous operations. Como escrito, esse código bloqueia o thread que o está executando, impedindo-o de realizar qualquer outra tarefa.As written, this code blocks the thread executing it from doing any other work. Ele não será interrompido enquanto qualquer uma das tarefas estiver em andamento.It won't be interrupted while any of the tasks are in progress. Seria como se você fixasse o olhar na torradeira depois de colocar o pão.It would be as though you stared at the toaster after putting the bread in. Você ignoraria qualquer pessoa que estivesse conversando com você até que a torrada pulasse.You'd ignore anyone talking to you until the toast popped.

Vamos começar atualizando esse código para que o thread não seja bloqueado enquanto houver tarefas em execução.Let's start by updating this code so that the thread doesn't block while tasks are running. A palavra-chave await oferece uma maneira sem bloqueio de iniciar uma tarefa e, em seguida, continuar a execução quando essa tarefa for concluída.The await keyword provides a non-blocking way to start a task, then continue execution when that task completes. Uma versão assíncrona simples do código de fazer café da manhã ficaria como o snippet a seguir: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!");
}

Esse código não bloqueia enquanto os ovos ou o bacon são preparados.This code doesn't block while the eggs or the bacon are cooking. Entretanto, esse código não iniciará outras tarefas.This code won't start any other tasks though. Você ainda colocaria o pão na torradeira e ficaria olhando até ele pular.You'd still put the toast in the toaster and stare at it until it pops. Mas, pelo menos, você responderia a qualquer pessoa que quisesse sua atenção.But at least, you'd respond to anyone that wanted your attention. Em um restaurante em que vários pedidos são feitos, o cozinheiro pode iniciar o preparo de outro café da manhã enquanto prepara o primeiro.In a restaurant where multiple orders are placed, the cook could start another breakfast while the first is cooking.

Agora, o thread trabalhando no café da manhã não fica bloqueado aguardando qualquer tarefa iniciada que ainda não tenha terminado.Now, the thread working on the breakfast isn't blocked while awaiting any started task that hasn't yet finished. Para alguns aplicativos, essa alteração já basta.For some applications, this change is all that's needed. Um aplicativo de GUI ainda responde ao usuário com apenas essa alteração.A GUI application still responds to the user with just this change. No entanto, neste cenário, você quer mais.However, for this scenario, you want more. Você não deseja que cada uma das tarefas componentes seja executada em sequência.You don't want each of the component tasks to be executed sequentially. É melhor iniciar cada uma das tarefas componentes antes de aguardar a conclusão da tarefa anterior.It's better to start each of the component tasks before awaiting the previous task's completion.

Iniciar tarefas simultaneamenteStart tasks concurrently

Em muitos cenários, convém iniciar várias tarefas independentes imediatamente.In many scenarios, you want to start several independent tasks immediately. Em seguida, conforme cada tarefa é concluída, você pode continuar outro trabalho que esteja pronto.Then, as each task finishes, you can continue other work that's ready. Na analogia do café da manhã, é assim que você prepara o café da manhã muito mais rapidamente.In the breakfast analogy, that's how you get breakfast done more quickly. Você também prepara tudo quase ao mesmo tempo.You also get everything done close to the same time. Você terá um café da manhã quente.You'll get a hot breakfast.

O System.Threading.Tasks.Task e os tipos relacionados são classes que você pode usar para pensar nas tarefas que estão em andamento.The System.Threading.Tasks.Task and related types are classes you can use to reason about tasks that are in progress. Elas permitem que você escreva código que se assemelhe mais à maneira como você realmente prepara o café da manhã.That enables you to write code that more closely resembles the way you'd actually create breakfast. Você começaria a preparar os ovos, o bacon e a torrada ao mesmo tempo.You'd start cooking the eggs, bacon, and toast at the same time. Como cada um exige ação, você voltaria sua atenção para essa tarefa, cuidaria da próxima ação e aguardaria algo mais que exigisse sua atenção.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.

Você inicia uma tarefa e espera o objeto Task que representa o trabalho.You start a task and hold on to the Task object that represents the work. Você vai await cada tarefa antes de trabalhar com o respectivo resultado.You'll await each task before working with its result.

Vamos fazer essas alterações no código do café da manhã.Let's make these changes to the breakfast code. A primeira etapa é armazenar as tarefas para as operações quando elas forem iniciadas, em vez de aguardá-las: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!");

Em seguida, você pode mover as instruções await do bacon e dos ovos até o final do método, antes de servir o café da manhã: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!");

O código anterior funciona melhor.The preceding code works better. Você inicia todas as tarefas assíncronas ao mesmo tempo.You start all the asynchronous tasks at once. Você aguarda cada tarefa somente quando precisar dos resultados.You await each task only when you need the results. O código anterior pode ser semelhante a um código em um aplicativo Web que faz solicitações de diferentes microsserviços e combina os resultados em uma única página.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. Você fará todas as solicitações imediatamente e, em seguida, await em todas essas tarefas e comporá a página da Web.You'll make all the requests immediately, then await all those tasks and compose the web page.

Composição com tarefasComposition with tasks

Você prepara tudo para o café da manhã ao mesmo tempo, exceto a torrada.You have everything ready for breakfast at the same time except the toast. Preparar a torrada é a composição de uma operação assíncrona (torrar o pão) com operações síncronas (adicionar a manteiga e a geleia).Making the toast is the composition of an asynchronous operation (toasting the bread), and synchronous operations (adding the butter and the jam). A atualização deste código ilustra um conceito importante:Updating this code illustrates an important concept:

Importante

A composição de uma operação assíncrona seguida por trabalho síncrono é uma operação assíncrona.The composition of an asynchronous operation followed by synchronous work is an asynchronous operation. Explicando de outra forma, se qualquer parte de uma operação for assíncrona, toda a operação será assíncrona.Stated another way, if any portion of an operation is asynchronous, the entire operation is asynchronous.

O código anterior mostrou que você pode usar objetos Task ou Task<TResult> para manter tarefas em execução.The preceding code showed you that you can use Task or Task<TResult> objects to hold running tasks. Você await em cada tarefa antes de usar seu resultado.You await each task before using its result. A próxima etapa é criar métodos que declarem a combinação de outro trabalho.The next step is to create methods that represent the combination of other work. Antes de servir o café da manhã, você quer aguardar a tarefa que representa torrar o pão antes de adicionar manteiga e geleia.Before serving breakfast, you want to await the task that represents toasting the bread before adding butter and jam. Você pode declarar esse trabalho com o código a seguir:You can represent that work with the following code:

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

O método anterior tem o modificador async na sua assinatura.The preceding method has the async modifier in its signature. Isso sinaliza ao compilador que esse método contém uma instrução await; ele contém operações assíncronas.That signals to the compiler that this method contains an await statement; it contains asynchronous operations. Este método representa a tarefa que torra o pão e, em seguida, adiciona manteiga e geleia.This method represents the task that toasts the bread, then adds butter and jam. Esse método retorna um Task<TResult> que representa a composição dessas três operações.This method returns a Task<TResult> that represents the composition of those three operations. O principal bloco de código agora se torna: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 plainToast = await ToastBreadAsync(number);
        ApplyButter(plainToast);
        ApplyJam(plainToast);
        return plainToast;
    }
}

A alteração anterior ilustrou uma técnica importante para trabalhar com código assíncrono.The previous change illustrated an important technique for working with asynchronous code. Você pode compor tarefas, separando as operações em um novo método que retorna uma tarefa.You compose tasks by separating the operations into a new method that returns a task. Você pode escolher quando aguardar essa tarefa.You can choose when to await that task. Você pode iniciar outras tarefas simultaneamente.You can start other tasks concurrently.

Aguardar tarefas com eficiênciaAwait tasks efficiently

A série de instruções await no final do código anterior pode ser melhorada usando métodos da classe Task.The series of await statements at the end of the preceding code can be improved by using methods of the Task class. Uma dessas APIs é a WhenAll, que retorna um Task que é concluído ao final de todas as tarefas na lista de argumentos, conforme mostrado no código a seguir: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!");

Outra opção é usar WhenAny, que retorna uma Task<Task> que é concluída quando qualquer um dos argumentos é concluído.Another option is to use WhenAny, which returns a Task<Task> that completes when any of its arguments completes. Você pode aguardar a tarefa retornada, sabendo que ela já foi concluída.You can await the returned task, knowing that it has already finished. O código a seguir mostra como você poderia usar WhenAny para aguardar a primeira tarefa concluir e, em seguida, processar seu resultado.The following code shows how you could use WhenAny to await the first task to finish and then process its result. Depois de processar o resultado da tarefa concluída, você remove essa tarefa concluída da lista de tarefas passada para 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!");

Depois de todas essas alterações, a versão final do Main fica parecida o código a seguir: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 plainToast = await ToastBreadAsync(number);
        ApplyButter(plainToast);
        ApplyJam(plainToast);
        return plainToast;
    }
}

Esse código final é assíncrono.This final code is asynchronous. Ele reflete mais precisamente como uma pessoa poderia preparar um café da manhã.It more accurately reflects how a person would cook a breakfast. Compare o código anterior com o primeiro exemplo de código neste artigo.Compare the preceding code with the first code sample in this article. As ações principais permanecem claras ao ler o código.The core actions are still clear from reading the code. Você pode ler esse código da mesma forma como faria ao ler essas instruções para fazer um café da manhã no início deste artigo.You can read this code the same way you'd read those instructions for making a breakfast at the beginning of this article. Os recursos de linguagem para async e await fornecem a tradução que todas as pessoas fazem para seguir essas instruções escritas: iniciar tarefas assim que possível e não ficar bloqueado ao aguardar a conclusão de tarefas.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.