Modelo de programação assíncrona de tarefa

É possível evitar gargalos de desempenho e aprimorar a resposta geral do seu aplicativo usando a programação assíncrona. No entanto, as técnicas tradicionais para escrever aplicativos assíncronos podem ser complicadas, dificultando sua escrita, depuração e manutenção.

O C# apresenta uma abordagem simplificada à programação assíncrona que faz uso do suporte assíncrono no runtime do .NET. O compilador faz o trabalho difícil que o desenvolvedor costumava fazer, e seu aplicativo mantém a estrutura lógica que se assemelha ao código síncrono. Como resultado, você obtém todas as vantagens da programação assíncrona com uma fração do esforço.

Este tópico oferece uma visão geral de quando e como usar a programação assíncrona e inclui links para tópicos de suporte que contêm detalhes e exemplos.

A assincronia melhora a capacidade de resposta

A assincronia é essencial para atividades que são potencialmente de bloqueio, como o acesso via Web. O acesso a um recurso da Web às vezes é lento ou atrasado. Se tal atividade for bloqueada em um processo síncrono, todo o aplicativo deverá esperar. Em um processo assíncrono, o aplicativo poderá prosseguir com outro trabalho que não dependa do recurso da Web até a tarefa potencialmente causadora do bloqueio terminar.

A tabela a seguir mostra a áreas típicas onde a programação assíncrona melhora a resposta. As APIs listadas do .NET e do Windows Runtime contêm métodos que dão suporte à programação assíncrona.

Área do aplicativo Tipos .NET com métodos assíncronos Tipos Windows Runtime com métodos assíncronos
Acesso à Web HttpClient Windows.Web.Http.HttpClient
SyndicationClient
Trabalhando com arquivos JsonSerializer
StreamReader
StreamWriter
XmlReader
XmlWriter
StorageFile
Trabalhando com imagens MediaCapture
BitmapEncoder
BitmapDecoder
Programação WCF Operações síncronas e assíncronas

A assincronia é especialmente importante para aplicativos que acessam o thread de interface de usuário porque todas as atividades relacionadas à interface do usuário normalmente compartilham um único thread. Se um processo for bloqueado em um aplicativo síncrono, todos serão bloqueados. Seu aplicativo para de responder, o que poderia levar você a concluir que ele falhou quando, na verdade, está apenas aguardando.

Quando você usa métodos assíncronos, o aplicativo continua a responder à interface do usuário. Você poderá redimensionar ou minimizar uma janela, por exemplo, ou fechar o aplicativo se você não desejar aguardar sua conclusão.

A abordagem baseada em assincronia adiciona o equivalente de uma transmissão automática à lista de opções disponíveis para escolha ao criar operações assíncronas. Ou seja, você obtém todos os benefícios da programação assíncrona tradicional, mas com muito menos esforço do desenvolvedor.

Métodos assíncronos são mais fáceis de escrever

As palavras-chave async e await em C# são a parte central da programação assíncrona. Ao usar essas duas palavras-chave, você poderá usar recursos do .NET Framework, do .NET Core ou do Windows Runtime para criar um método assíncrono quase que tão facilmente quanto criar um método síncrono. Os métodos assíncronos que você define usando a palavra-chave async são chamados de métodos assíncronos.

O exemplo a seguir mostra um método assíncrono. Você deve estar familiarizado com quase tudo no código.

Você pode encontrar um exemplo completo de WPF (Windows Presentation Foundation) disponível para download na Programação assíncrona com async e await em C#.

public async Task<int> GetUrlContentLengthAsync()
{
    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...");
}

Você pode aprender várias práticas com a amostra anterior. Comece com a assinatura do método. Ele inclui o modificador async. O tipo de retorno é Task<int> (confira a seção "Tipos de retorno" para obter mais opções). O nome do método termina com Async. No corpo do método, GetStringAsync retorna uma Task<string>. Isso significa que, quando você executar await em uma tarefa, obterá uma string (contents). Antes de aguardar a tarefa, você poderá fazer um trabalho que não dependa da string em GetStringAsync.

Preste muita atenção no operador await. Ele suspende GetUrlContentLengthAsync:

  • GetUrlContentLengthAsync não poderá continuar enquanto getStringTask não for concluída.
  • Enquanto isso, o controle é retornado ao chamador de GetUrlContentLengthAsync.
  • O controle será retomado aqui quando a getStringTask for concluída.
  • Em seguida, o operador await recupera o resultado string de getStringTask.

A instrução de retorno especifica um resultado inteiro. Os métodos que estão aguardando GetUrlContentLengthAsync recuperar o valor de comprimento.

Se GetUrlContentLengthAsync não tiver nenhum trabalho que possa fazer entre chamar GetStringAsync e aguardar a conclusão, você poderá simplificar o código ao chamar e esperar na instrução única a seguir.

string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");

As seguintes características resumem o que transforma o exemplo anterior em um método assíncrono:

  • A assinatura do método inclui um modificador async.

  • O nome de um método assíncrono, por convenção, termina com um sufixo "Async".

  • O tipo de retorno é um dos seguintes tipos:

    • Task<TResult> se o método possui uma instrução de retorno em que o operando tem o tipo TResult.
    • Task se o método não possui instrução de retorno alguma ou se ele possui uma instrução de retorno sem operando.
    • void se você estiver escrevendo um manipulador de eventos assíncronos.
    • Qualquer outro tipo que tenha um método GetAwaiter.

    Para obter mais informações, confira a seção Tipos e parâmetros de retorno.

  • O método geralmente inclui, pelo menos, uma expressão await, que marca um ponto em que o método não poderá continuar enquanto a operação assíncrona aguardada não for concluída. Enquanto isso, o método é suspenso e o controle retorna para o chamador do método. A próxima seção deste tópico ilustra o que acontece no ponto de suspensão.

Em métodos assíncronos, você usa as palavras-chave e os tipos fornecidos para indicar o que deseja fazer, e o compilador faz o resto, inclusive acompanhar o que deve acontecer quando o controle retorna a um ponto de espera em um método suspenso. Alguns processos de rotina, como loops e a manipulação de exceções, podem ser difíceis de manipular em um código assíncrono tradicional. Em um método assíncrono, você escreve esses elementos da mesma forma que faria em uma solução síncrona, e o problema é resolvido.

Para obter mais informações sobre assincronia nas versões anteriores do .NET Framework, confira Programação assíncrona do .NET Framework tradicional e do TPL.

O que acontece em um método assíncrono

O mais importante que você deve compreender na programação assíncrona é a forma como o fluxo de controle avança de um método para outro. O diagrama a seguir pode ser usado para conduzi-lo pelo processo:

Trace navigation of async control flow

Os números do diagrama correspondem às etapas a seguir, iniciadas quando um método de chamada chama o método assíncrono.

  1. Um método de chamada chama e aguarda o método assíncrono GetUrlContentLengthAsync.

  2. GetUrlContentLengthAsync cria uma instância de HttpClient e chama o método assíncrono GetStringAsync para baixar o conteúdo de um site como uma cadeia de caracteres.

  3. Algo acontece em GetStringAsync que suspende o andamento. Talvez ele deva aguardar o download de um site ou alguma outra atividade causadora de bloqueio. Para evitar o bloqueio de recursos, GetStringAsync transfere o controle para seu chamador, GetUrlContentLengthAsync.

    GetStringAsync retorna um Task<TResult>, em que TResult é uma cadeia de caracteres, e GetUrlContentLengthAsync atribui a tarefa à variável getStringTask. A tarefa representa o processo contínuo para a chamada a GetStringAsync, com um compromisso de produzir um valor de cadeia de caracteres real quando o trabalho estiver concluído.

  4. Como o getStringTask ainda não foi esperado, GetUrlContentLengthAsync pode continuar com outro trabalho que não depende do resultado final de GetStringAsync. O trabalho é representado por uma chamada ao método síncrono DoIndependentWork.

  5. DoIndependentWork é um método síncrono que faz seu trabalho e retorna ao seu chamador.

  6. GetUrlContentLengthAsync está sem trabalho que ele possa executar sem um resultado de getStringTask. Em seguida, GetUrlContentLengthAsync deseja calcular e retornar o comprimento da cadeia de caracteres baixada, mas o método não poderá calcular o valor enquanto o método tiver a cadeia de caracteres.

    Portanto, GetUrlContentLengthAsync usa um operador await para suspender seu andamento e para transferir o controle para o método que chamou GetUrlContentLengthAsync. GetUrlContentLengthAsync retorna um Task<int> ao chamador. A tarefa representa uma promessa de produzir um resultado inteiro que é o comprimento da cadeia de caracteres baixada.

    Observação

    Se GetStringAsync (e, portanto, getStringTask) for concluído antes que GetUrlContentLengthAsync o aguarde, o controle permanecerá em GetUrlContentLengthAsync. A despesa de suspender e depois retornar para GetUrlContentLengthAsync seria desperdiçada caso o processo assíncrono chamado getStringTask já tivesse sido concluído e GetUrlContentLengthAsync não tivesse que aguardar o resultado final.

    Dentro do método de chamada, o padrão de processamento continua. O chamador pode fazer outro trabalho que não dependa do resultado de GetUrlContentLengthAsync antes de aguardar o resultado, ou o chamador pode aguardar imediatamente. O método de chamada está aguardando GetUrlContentLengthAsync e GetUrlContentLengthAsync está aguardando GetStringAsync.

  7. GetStringAsync completa e produz um resultado de cadeia de caracteres. O resultado da cadeia de caracteres não é retornado pela chamada para GetStringAsync da maneira que você poderia esperar. (Lembre-se que o método já retornou uma tarefa na etapa 3.) Em vez disso, o resultado da cadeia de caracteres é armazenado na tarefa que representa a conclusão do método, getStringTask. O operador await recupera o resultado de getStringTask. A instrução de atribuição atribui o resultado retornado a contents.

  8. Quando GetUrlContentLengthAsync tem o resultado da cadeia de caracteres, o método pode calcular o comprimento da cadeia de caracteres. Em seguida, o trabalho de GetUrlContentLengthAsync também é concluído e o manipulador de eventos de espera poderá retomar. No exemplo completo no final do tópico, é possível confirmar que o manipulador de eventos recuperou e imprimiu o valor do comprimento do resultado. Se você não tiver experiência em programação assíncrona, considere por um minuto a diferença entre o comportamento síncrono e o assíncrono. Um método síncrono retorna quando seu trabalho é concluído (etapa 5), mas um método assíncrono retorna um valor de tarefa quando seu trabalho está suspenso (etapas 3 e 6). Quando o método assíncrono eventualmente concluir seu trabalho, a tarefa será marcada como concluída e o resultado, se houver, será armazenado na tarefa.

Métodos assíncronos da API

Você pode estar curioso para saber onde encontrar métodos como GetStringAsync que oferecem suporte à programação assíncrona. O .NET Framework 4.5 ou versão superior e o .NET Core contêm muitos membros que funcionam com async e com await. É possível reconhecê-los pelo sufixo “Async” que é acrescentado ao nome do membro e pelo tipo de retorno de Task ou de Task<TResult>. Por exemplo, a classe System.IO.Stream contém métodos como CopyToAsync, ReadAsync e WriteAsync, juntamente com os métodos síncronos CopyTo, Read e Write.

O Windows Runtime também contém vários métodos que você pode usar com async e await em aplicativos do Windows. Para obter mais informações, veja Threading e programação assíncrona para o desenvolvimento da UWP e Programação assíncrona (aplicativos da Windows Store) e Início Rápido: chamando APIs assíncronas em C# ou Visual Basic se você usa versões anteriores do Windows Runtime.

Threads

Os métodos assíncronos destinam-se a ser operações não causadoras de bloqueios. Uma expressão await em um método assíncrono não bloqueia o thread atual enquanto a tarefa aguardada está em execução. Em vez disso, a expressão anterior assina o restante do método como uma continuação e retorna o controle para o chamador do método assíncrono.

As palavras-chave async e await não fazem com que threads adicionais sejam criados. Os métodos assíncronos não exigem multithreading, pois um método assíncrono não executa em seu próprio thread. O método é executado no contexto de sincronização atual e usa tempo no thread somente quando o método está ativo. É possível usar Task.Run para mover o trabalho de CPU associado a um thread em segundo plano, mas um thread em segundo plano não ajuda com um processo que está apenas aguardando que os resultados tornem-se disponíveis.

A abordagem baseada em async para a programação assíncrona é preferível às abordagens existentes em quase todos os casos. Essa abordagem é especialmente mais eficiente do que a classe BackgroundWorker para operações de entrada e saída, porque o código é mais simples e você não precisa se proteger contra condições de corrida. Em combinação com o método Task.Run, a programação assíncrona é melhor que BackgroundWorker para operações associadas à CPU, porque a programação assíncrona separa os detalhes de coordenação da execução do código de trabalho que Task.Run transfere ao pool de threads.

async e await

Se você especificar que um método é assíncrono usando um modificador async, você habilitará os dois recursos a seguir.

  • O método assíncrono marcado pode usar await para designar pontos de suspensão. O operador await informa ao compilador que o método assíncrono não poderá continuar além daquele ponto até que o processo assíncrono aguardado seja concluído. Enquanto isso, o controle retorna para o chamador do método assíncrono.

    A suspensão de um método assíncrono em uma expressão await não constitui uma saída de método e os blocos finally não são executados.

  • O método assíncrono marcado pode ele próprio ser aguardado por métodos que o chamam.

Um método assíncrono normalmente contém uma ou mais ocorrências do operador await, mas a ausência de expressões await não causa erro de compilação. Se um método assíncrono não usa o operador await para marcar um ponto de suspensão, o método é executado da mesma forma que um método síncrono, independentemente do modificador async. O compilador emite um aviso para esses métodos.

async e await são palavras-chave contextuais. Para obter mais informações e exemplos, consulte os seguintes tópicos:

Tipos e parâmetros de retorno

Um método assíncrono normalmente retorna Task ou Task<TResult>. Dentro de um método assíncrono, um operador await é aplicado a uma tarefa que é retornada de uma chamada para outro método assíncrono.

Você especificará Task<TResult> como o tipo de retorno se o método contiver uma instrução return que especifica um operando do tipo TResult.

Você usará Task como o tipo de retorno caso o método não possua nenhuma instrução return ou tenha uma instrução return que não retorna um operando.

Você também pode especificar qualquer outro tipo de retorno, desde que o tipo inclua um método GetAwaiter. ValueTask<TResult> é um exemplo de tal tipo. Ele está disponível no pacote NuGet System.Threading.Tasks.Extension.

O seguinte exemplo mostra como você declara e chama um método que retorna Task<TResult> ou 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();

Cada tarefa retornada representa um trabalho em andamento. Uma tarefa encapsula informações sobre o estado do processo assíncrono e, consequentemente, o resultado final do processo ou a exceção que o processo apresenta quando não é bem-sucedido.

Um método assíncrono também pode ter um tipo de retorno void. Esse tipo de retorno é usado principalmente para definir manipuladores de eventos, onde o tipo de retorno void é necessário. Os manipuladores de eventos assíncronos geralmente servem como o ponto de partida para programas assíncronos.

Um método assíncrono que tem o tipo de retorno void não pode ser esperado, e o chamador de um método que retorna nulo não pode capturar nenhuma exceção lançada pelo método.

O método não pode declarar nenhum parâmetro in, ref ou out, mas pode chamar métodos com tais parâmetros. Da mesma forma, um método assíncrono não pode retornar um valor por referência, embora possa chamar métodos com valores retornados ref.

Para obter mais informações e exemplos, confira Tipos de retorno assíncronos (C#).

As APIs assíncronas na programação do Windows Runtime têm um dos seguintes tipos de retorno, que são semelhantes às tarefas:

Convenção de nomenclatura

Por convenção, os métodos que geralmente retornam tipos esperáveis (por exemplo, Task, Task<T>, ValueTask e ValueTask<T>) devem ter nomes que terminam com "Async". Os métodos que iniciam uma operação assíncrona, mas não retornam um tipo aguardável não devem ter nomes que terminam com "Async", mas podem começar com "Begin", "Start" ou algum outro verbo que indique que esse método não retorna nem gera o resultado da operação.

É possível ignorar a convenção quando um evento, uma classe base ou um contrato de interface sugere um nome diferente. Por exemplo, você não deve renomear manipuladores de eventos comuns, como OnButtonClick.

Artigos relacionados (Visual Studio)

Title Descrição
Como fazer várias solicitações da Web em paralelo usando async e await (C#) Demonstra como iniciar várias tarefas ao mesmo tempo.
Tipos de retorno assíncronos (C#) Ilustra os tipos que os métodos assíncronos podem retornar e explica quando cada tipo é apropriado.
Cancelar tarefas com um token de cancelamento como um mecanismo de sinalização. Mostra como adicionar a seguinte funcionalidade à sua solução assíncrona:

- Cancelar uma lista de tarefas (C#)
- Cancelar tarefas após um período (C#)
- Processar tarefas assíncronas conforme elas forem concluídas (C#)
Como usar o Async para acessar arquivos (C#) Lista e demonstra as vantagens de usar async e await para acessar arquivos.
TAP (padrão assíncrono baseado em tarefas) Descreve um padrão assíncrono, o padrão é baseado nos tipos Task e Task<TResult>.
Vídeos sobre assincronia no Channel 9 Fornece links para uma variedade de vídeos sobre programação assíncrona.

Confira também