Programação assíncronaAsynchronous programming

Se você tiver alguma necessidade de ligação de e/s (como solicitar dados de uma rede, acessar um banco de dado ou ler e gravar em um sistema de arquivos), você desejará utilizar a programação assíncrona.If you have any I/O-bound needs (such as requesting data from a network, accessing a database, or reading and writing to a file system), you'll want to utilize asynchronous programming. Você também pode ter código vinculado à CPU, como a execução de um cálculo dispendioso, que também é um bom cenário para escrever código assíncrono.You could also have CPU-bound code, such as performing an expensive calculation, which is also a good scenario for writing async code.

O C# tem um modelo de programação assíncrona de nível de linguagem, que permite escrever facilmente um código assíncrono sem precisar fazer malabarismos com retornos de chamada ou estar em conformidade com uma biblioteca com suporte para assincronia.C# has a language-level asynchronous programming model, which allows for easily writing asynchronous code without having to juggle callbacks or conform to a library that supports asynchrony. Ele segue o que é conhecido como TAP (Padrão assíncrono baseado em tarefa).It follows what is known as the Task-based Asynchronous Pattern (TAP).

Visão geral do modelo assíncronoOverview of the asynchronous model

O núcleo da programação assíncrona são os objetos Task e Task<T>, que modelam as operações assíncronas.The core of async programming is the Task and Task<T> objects, which model asynchronous operations. Eles têm suporte das palavras-chave async e await.They are supported by the async and await keywords. O modelo é bastante simples na maioria dos casos:The model is fairly simple in most cases:

  • Para O código ligado à e/s, você aguarda uma operação que retorna um Task ou Task<T> dentro de um async método.For I/O-bound code, you await an operation that returns a Task or Task<T> inside of an async method.
  • Para código vinculado à CPU, você aguarda uma operação iniciada em um thread em segundo plano com o Task.Run método.For CPU-bound code, you await an operation that is started on a background thread with the Task.Run method.

É na palavra-chave await que a mágica acontece.The await keyword is where the magic happens. Ela cede o controle para o chamador do método que executou await e, em última instância, permite que uma interface do usuário tenha capacidade de resposta ou que um serviço seja elástico.It yields control to the caller of the method that performed await, and it ultimately allows a UI to be responsive or a service to be elastic. Embora haja maneiras de abordar um código assíncrono diferente de async e await , este artigo se concentra nas construções de nível de linguagem.While there are ways to approach async code other than async and await, this article focuses on the language-level constructs.

Exemplo associado de e/s: baixar dados de um serviço WebI/O-bound example: Download data from a web service

Talvez seja necessário baixar alguns dados de um serviço Web quando um botão for pressionado, mas não quiser bloquear o thread da interface do usuário.You may need to download some data from a web service when a button is pressed but don't want to block the UI thread. Isso pode ser feito da seguinte maneira:It can be accomplished like this:

private readonly HttpClient _httpClient = new HttpClient();

downloadButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI as the request
    // from the web service is happening.
    //
    // The UI thread is now free to perform other work.
    var stringData = await _httpClient.GetStringAsync(URL);
    DoSomethingWithData(stringData);
};

O código expressa a intenção (Baixando dados de forma assíncrona) sem sobrecarga-lo na interação com Task objetos.The code expresses the intent (downloading data asynchronously) without getting bogged down in interacting with Task objects.

Exemplo associado à CPU: executar um cálculo para um jogoCPU-bound example: Perform a calculation for a game

Digamos que você está escrevendo um jogo para dispositivo móvel em que, ao pressionar um botão, poderá causar danos a muitos inimigos na tela.Say you're writing a mobile game where pressing a button can inflict damage on many enemies on the screen. A realização do cálculo de dano pode ser dispendiosa e fazê-lo no thread da interface do usuário faria com que o jogo parecesse pausar durante a realização do cálculo!Performing the damage calculation can be expensive, and doing it on the UI thread would make the game appear to pause as the calculation is performed!

A melhor maneira de lidar com isso é iniciar um thread em segundo plano, que faz o trabalho usando Task.Run e aguardar seu resultado usando await .The best way to handle this is to start a background thread, which does the work using Task.Run, and await its result using await. Isso permite que a interface do usuário fique tranqüila, pois o trabalho está sendo feito.This allows the UI to feel smooth as the work is being done.

private DamageResult CalculateDamageDone()
{
    // Code omitted:
    //
    // Does an expensive calculation and returns
    // the result of that calculation.
}

calculateButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI while CalculateDamageDone()
    // performs its work. The UI thread is free to perform other work.
    var damageResult = await Task.Run(() => CalculateDamageDone());
    DisplayDamage(damageResult);
};

Esse código claramente expressa a intenção do evento de clique do botão, ele não requer o gerenciamento manual de um thread em segundo plano e faz isso em uma forma sem bloqueio.This code clearly expresses the intent of the button's click event, it doesn't require managing a background thread manually, and it does so in a non-blocking way.

O que acontece nos bastidoresWhat happens under the covers

Há muitas partes móveis nas quais as operações assíncronas se preocupam.There are many moving pieces where asynchronous operations are concerned. Se você estiver curioso sobre o que está acontecendo nos bastidores do Task e do Task<T> , consulte o artigo sobre o Async em profundidade para obter mais informações.If you're curious about what's happening underneath the covers of Task and Task<T>, see the Async in-depth article for more information.

No lado do C# das coisas, o compilador transforma seu código em um computador de estado que controla coisas como produzir a execução quando um await é atingido e retomar a execução quando um trabalho em segundo plano é concluído.On the C# side of things, the compiler transforms your code into a state machine that keeps track of things like yielding execution when an await is reached and resuming execution when a background job has finished.

Para os que gostam da teoria, essa é uma implementação do Modelo Promise de assincronia.For the theoretically-inclined, this is an implementation of the Promise Model of asynchrony.

Principais partes a serem compreendidasKey pieces to understand

  • O código assíncrono pode ser usado tanto para o código vinculado à E/S quanto vinculado à CPU, mas de maneira diferente para cada cenário.Async code can be used for both I/O-bound and CPU-bound code, but differently for each scenario.
  • O código assíncrono usa Task<T> e Task, que são constructos usados para modelar o trabalho que está sendo feito em segundo plano.Async code uses Task<T> and Task, which are constructs used to model work being done in the background.
  • A palavra-chave async transforma um método em um método assíncrono, o que permite que você use a palavra-chave await em seu corpo.The async keyword turns a method into an async method, which allows you to use the await keyword in its body.
  • Quando a palavra-chave await é aplicada, ela suspende o método de chamada e transfere o controle de volta ao seu chamador até que a tarefa em espera seja concluída.When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.
  • A await só pode ser usada dentro de um método assíncrono.await can only be used inside an async method.

Reconhecer O trabalho associado à CPU e à e/sRecognize CPU-bound and I/O-bound work

Os dois primeiros exemplos deste guia mostraram como você pode usar async e await para O trabalho vinculado de e/s e de CPU.The first two examples of this guide showed how you could use async and await for I/O-bound and CPU-bound work. É a chave que você pode identificar quando um trabalho que você precisa fazer é associado à e/s ou à CPU, pois ele pode afetar muito o desempenho do seu código e pode potencialmente levar ao uso indevido de determinadas construções.It's key that you can identify when a job you need to do is I/O-bound or CPU-bound because it can greatly affect the performance of your code and could potentially lead to misusing certain constructs.

Aqui estão duas perguntas que devem ser feitas antes de escrever qualquer código:Here are two questions you should ask before you write any code:

  1. Seu código ficará em "espera" por alguma coisa, como dados de um banco de dados?Will your code be "waiting" for something, such as data from a database?

    Se a resposta é "sim", seu trabalho é vinculado à E/S.If your answer is "yes", then your work is I/O-bound.

  2. Seu código estará executando uma computação cara?Will your code be performing an expensive computation?

    Se você respondeu "sim", seu trabalho é vinculado à CPU.If you answered "yes", then your work is CPU-bound.

Se o seu trabalho for vinculado à E/S, use async e await sem Task.Run.If the work you have is I/O-bound, use async and await without Task.Run. Você não deve usar a biblioteca de paralelismo de tarefas.You should not use the Task Parallel Library. O motivo disso é descrito em Async em profundidade.The reason for this is outlined in Async in Depth.

Se o trabalho que você tem está vinculado à CPU e você se preocupa com a capacidade de resposta, use async and await , mas gera o trabalho em outro thread com Task.Run .If the work you have is CPU-bound and you care about responsiveness, use async and await, but spawn off the work on another thread with Task.Run. Se o trabalho for apropriado para simultaneidade e paralelismo, considere também usar a biblioteca de tarefas paralelas.If the work is appropriate for concurrency and parallelism, also consider using the Task Parallel Library.

Além disso, você sempre deve medir a execução do seu código.Additionally, you should always measure the execution of your code. Por exemplo, talvez você tenha uma situação em que seu trabalho vinculado à CPU não é caro o suficiente em comparação com os custos gerais das trocas de contexto ao realizar o multithreading.For example, you may find yourself in a situation where your CPU-bound work is not costly enough compared with the overhead of context switches when multithreading. Cada opção tem vantagens e desvantagens e você deve escolher o que é correto para a sua situação.Every choice has its tradeoff, and you should pick the correct tradeoff for your situation.

Mais exemplosMore examples

Os exemplos a seguir demonstram várias maneiras para escrever código assíncrono no C#.The following examples demonstrate various ways you can write async code in C#. Elas abordam alguns cenários diferentes que você pode encontrar.They cover a few different scenarios you may come across.

Extrair dados de uma redeExtract data from a network

Esse trecho de código baixa o HTML da Home Page em https://dotnetfoundation.org e conta o número de vezes que a cadeia de caracteres ".net" ocorre no HTML.This snippet downloads the HTML from the homepage at https://dotnetfoundation.org and counts the number of times the string ".NET" occurs in the HTML. Ele usa ASP.NET para definir um método de controlador da API Web, que executa essa tarefa e retorna o número.It uses ASP.NET to define a Web API controller method, which performs this task and returns the number.

Observação

Se você pretende fazer análise de HTML no código de produção, não use expressões regulares.If you plan on doing HTML parsing in production code, don't use regular expressions. Use uma biblioteca de análise.Use a parsing library instead.

private readonly HttpClient _httpClient = new HttpClient();

[HttpGet, Route("DotNetCount")]
public async Task<int> GetDotNetCount()
{
    // Suspends GetDotNetCount() to allow the caller (the web server)
    // to accept another request, rather than blocking on this one.
    var html = await _httpClient.GetStringAsync("https://dotnetfoundation.org");

    return Regex.Matches(html, @"\.NET").Count;
}

Aqui está o mesmo cenário escrito para um aplicativo universal do Windows, que executa a mesma tarefa quando um botão for pressionado:Here's the same scenario written for a Universal Windows App, which performs the same task when a Button is pressed:

private readonly HttpClient _httpClient = new HttpClient();

private async void OnSeeTheDotNetsButtonClick(object sender, RoutedEventArgs e)
{
    // Capture the task handle here so we can await the background task later.
    var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("https://dotnetfoundation.org");

    // Any other work on the UI thread can be done here, such as enabling a Progress Bar.
    // This is important to do here, before the "await" call, so that the user
    // sees the progress bar before execution of this method is yielded.
    NetworkProgressBar.IsEnabled = true;
    NetworkProgressBar.Visibility = Visibility.Visible;

    // The await operator suspends OnSeeTheDotNetsButtonClick(), returning control to its caller.
    // This is what allows the app to be responsive and not block the UI thread.
    var html = await getDotNetFoundationHtmlTask;
    int count = Regex.Matches(html, @"\.NET").Count;

    DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}";

    NetworkProgressBar.IsEnabled = false;
    NetworkProgressBar.Visibility = Visibility.Collapsed;
}

Aguardar a conclusão de várias tarefasWait for multiple tasks to complete

Você pode encontrar em uma situação em que precisa recuperar várias partes de dados simultaneamente.You may find yourself in a situation where you need to retrieve multiple pieces of data concurrently. A Task API contém dois métodos Task.WhenAll e Task.WhenAny , que permitem que você escreva um código assíncrono que executa uma espera sem bloqueio em vários trabalhos em segundo plano.The Task API contains two methods, Task.WhenAll and Task.WhenAny, that allow you to write asynchronous code that performs a non-blocking wait on multiple background jobs.

Este exemplo mostra como você pode obter os dados User para um conjunto de userIds.This example shows how you might grab User data for a set of userIds.

public async Task<User> GetUserAsync(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = new List<Task<User>>();
    foreach (int userId in userIds)
    {
        getUserTasks.Add(GetUserAsync(userId));
    }

    return await Task.WhenAll(getUserTasks);
}

Esta é outra maneira de escrever isso de forma mais sucinta, usando LINQ:Here's another way to write this more succinctly, using LINQ:

public async Task<User> GetUserAsync(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = userIds.Select(id => GetUserAsync(id));
    return await Task.WhenAll(getUserTasks);
}

Embora seja menos código, tome cuidado ao misturar LINQ com código assíncrono.Although it's less code, use caution when mixing LINQ with asynchronous code. Como o LINQ utiliza a execução adiada (lenta), as chamadas assíncronas não acontecerão imediatamente como em um loop foreach, a menos que você force a sequência gerada a iterar com uma chamada a .ToList() ou .ToArray().Because LINQ uses deferred (lazy) execution, async calls won't happen immediately as they do in a foreach loop unless you force the generated sequence to iterate with a call to .ToList() or .ToArray().

Informações importantes e conselhosImportant info and advice

Com a programação assíncrona, há alguns detalhes a serem considerados, o que pode impedir um comportamento inesperado.With async programming, there are some details to keep in mind which can prevent unexpected behavior.

  • Os métodos async precisam ter uma palavra-chave await no corpo ou eles nunca transferirão!async methods need to have an await keyword in their body or they will never yield!

    É importante ter isso em mente.This is important to keep in mind. Se await não for usado no corpo de um async método, o compilador C# gerará um aviso, mas o código será compilado e executado como se fosse um método normal.If await is not used in the body of an async method, the C# compiler generates a warning, but the code compiles and runs as if it were a normal method. Isso é incrivelmente ineficiente, pois a máquina de estado gerada pelo compilador C# para o método assíncrono não está realizando nada.This is incredibly inefficient, as the state machine generated by the C# compiler for the async method is not accomplishing anything.

  • Você deve adicionar "Async" como o sufixo de cada nome de método assíncrono que escrever.You should add "Async" as the suffix of every async method name you write.

Essa é a convenção usada no .NET para diferenciar mais facilmente os métodos síncronos e assíncronos.This is the convention used in .NET to more easily differentiate synchronous and asynchronous methods. Determinados métodos que não são chamados explicitamente pelo seu código (como manipuladores de eventos ou métodos de controlador da Web) não necessariamente se aplicam.Certain methods that aren't explicitly called by your code (such as event handlers or web controller methods) don't necessarily apply. Como eles não são chamados explicitamente pelo seu código, ser explícito sobre a nomenclatura não é tão importante.Because they are not explicitly called by your code, being explicit about their naming isn't as important.

  • O async void só deve ser usado para manipuladores de eventos.async void should only be used for event handlers.

O async void é a única maneira de permitir que os manipuladores de eventos assíncronos trabalhem, pois os eventos não têm tipos de retorno (portanto, não podem fazer uso de Task e Task<T>).async void is the only way to allow asynchronous event handlers to work because events do not have return types (thus cannot make use of Task and Task<T>). Qualquer outro uso de async void não segue o modelo TAP e pode ser um desafio utilizá-lo, como:Any other use of async void does not follow the TAP model and can be challenging to use, such as:

  • Exceções geradas em um async void método não podem ser detectadas fora desse método.Exceptions thrown in an async void method can't be caught outside of that method.

  • async void os métodos são difíceis de testar.async void methods are difficult to test.

  • async void os métodos podem causar efeitos colaterais ruins se o chamador não estiver esperando que eles sejam assíncronos.async void methods can cause bad side effects if the caller isn't expecting them to be async.

  • Vá com cuidado ao usar lambdas assíncronas em expressões LINQTread carefully when using async lambdas in LINQ expressions

As expressões lambda no LINQ usam a execução adiada, o que significa que o código pode acabar sendo executado por vez, quando você não está esperando.Lambda expressions in LINQ use deferred execution, meaning code could end up executing at a time when you're not expecting it to. A introdução de tarefas de bloqueio no meio disso poderia facilmente resultar em um deadlock, se não estivessem escritas corretamente.The introduction of blocking tasks into this can easily result in a deadlock if not written correctly. Além disso, o aninhamento de código assíncrono dessa maneira também pode dificultar a ponderação a respeito da execução do código.Additionally, the nesting of asynchronous code like this can also make it more difficult to reason about the execution of the code. O Async e o LINQ são poderosos, mas devem ser usados em conjunto com o cuidado e o mais claro possível.Async and LINQ are powerful but should be used together as carefully and clearly as possible.

  • Escrever código que aguarda tarefas de uma maneira sem bloqueioWrite code that awaits Tasks in a non-blocking manner

O bloqueio do thread atual como um meio de esperar por um Task para concluir pode resultar em deadlocks e threads de contexto bloqueados e pode exigir tratamento de erros mais complexo.Blocking the current thread as a means to wait for a Task to complete can result in deadlocks and blocked context threads and can require more complex error-handling. A tabela a seguir fornece orientação sobre como lidar com a espera de tarefas de uma maneira sem bloqueio:The following table provides guidance on how to deal with waiting for tasks in a non-blocking way:

Use isto...Use this... Em vez disto...Instead of this... Quando desejar fazer isso...When wishing to do this...
await Task.Wait ou Task.ResultTask.Wait or Task.Result Recuperação do resultado de uma tarefa em segundo planoRetrieving the result of a background task
await Task.WhenAny Task.WaitAny Aguardar a conclusão de qualquer tarefaWaiting for any task to complete
await Task.WhenAll Task.WaitAll Aguardar a conclusão de todas as tarefasWaiting for all tasks to complete
await Task.Delay Thread.Sleep Aguardar por um período de tempoWaiting for a period of time
  • Considere usar ValueTask sempre que possívelConsider using ValueTask where possible

Retornar um objeto Task de métodos assíncronos pode introduzir gargalos de desempenho em determinados caminhos.Returning a Task object from async methods can introduce performance bottlenecks in certain paths. Task é um tipo de referência, portanto, usá-lo significa alocar um objeto.Task is a reference type, so using it means allocating an object. Nos casos em que um método declarado com o async modificador retorna um resultado em cache ou é concluído de forma síncrona, as alocações extras podem se tornar um custo de tempo significativo nas seções críticas de desempenho do código.In cases where a method declared with the async modifier returns a cached result or completes synchronously, the extra allocations can become a significant time cost in performance critical sections of code. Isso pode se tornar caro se essas alocações ocorrem em loops rígidos.It can become costly if those allocations occur in tight loops. Para obter mais informações, consulte tipos de retorno assíncrono generalizados.For more information, see generalized async return types.

  • Considere usar ConfigureAwait(false)Consider using ConfigureAwait(false)

Uma pergunta comum é "quando devo usar o Task.ConfigureAwait(Boolean) método?".A common question is, "when should I use the Task.ConfigureAwait(Boolean) method?". O método permite que uma Task instância configure seu aguardador.The method allows for a Task instance to configure its awaiter. Essa é uma consideração importante e a configuração incorreta pode potencialmente ter implicações de desempenho e até mesmo deadlocks.This is an important consideration and setting it incorrectly could potentially have performance implications and even deadlocks. Para obter mais informações sobre ConfigureAwait o, consulte as perguntas frequentes do ConfigureAwait.For more information on ConfigureAwait, see the ConfigureAwait FAQ.

  • Escrever código com menos monitoração de estadoWrite less stateful code

Não dependa do estado de objetos globais ou da execução de determinados métodos.Don't depend on the state of global objects or the execution of certain methods. Em vez disso, depender apenas dos valores retornados dos métodos.Instead, depend only on the return values of methods. Por quê?Why?

  • Será mais fácil raciocinar sobre o código.Code will be easier to reason about.
  • O código será mais fácil de testar.Code will be easier to test.
  • Misturar código assíncrono e síncrono será muito mais simples.Mixing async and synchronous code is far simpler.
  • As condições de corrida poderão, normalmente, ser completamente evitadas.Race conditions can typically be avoided altogether.
  • Dependendo dos valores retornados, a coordenação de código assíncrono se tornará simples.Depending on return values makes coordinating async code simple.
  • (Bônus) funciona muito bem com a injeção de dependência.(Bonus) it works really well with dependency injection.

Uma meta recomendada é alcançar a Transparência referencial completa ou quase completa em seu código.A recommended goal is to achieve complete or near-complete Referential Transparency in your code. Isso resultará em uma base de código extremamente previsível, testável e de fácil manutenção.Doing so will result in an extremely predictable, testable, and maintainable codebase.

Outros recursosOther resources