Dominando C# Async e Await
A programação assíncrona pode parecer intimidante devido à sua terminologia complexa, mas oferece benefícios significativos, tornando os aplicativos mais rápidos e responsivos. O vídeo de Tim Corey sobre " C# Async/Await - Torne seu aplicativo mais responsivo e rápido com programação assíncrona " fornece um guia prático sobre como usar esses recursos de forma eficaz.
Neste artigo, usaremos o vídeo detalhado de Tim Corey para explicar esses conceitos, demonstrando as aplicações práticas de async e await por meio de exemplos claros. Abordaremos diversos cenários, desde manipuladores de eventos assíncronos até o gerenciamento de múltiplas tarefas, contextos de sincronização, tratamento de erros e as armadilhas comuns, como o uso de métodos assíncronos com a propriedade void. Ao final deste artigo, você terá um sólido entendimento de programação assíncrona e de como implementá-la em suas próprias aplicações C#.
Introdução
A programação assíncrona em C# é essencial para melhorar o desempenho das aplicações, especialmente ao trabalhar com operações que dependem muito de E/S ou de CPU. As palavras-chave async e await permitem que os desenvolvedores escrevam código assíncrono que pode executar tarefas de longa duração sem bloquear o thread principal. Ao marcar um método com a palavra-chave async, você sinaliza que o método realizará uma operação assíncrona, retornando uma Task ou Task<t>. A palavra-chave await é usada dentro de um método async para pausar a execução até que a tarefa aguardada seja concluída, permitindo que outras operações sejam executadas simultaneamente.
Ao contrário da programação síncrona, em que as tarefas são executadas sequencialmente e podem bloquear a thread de chamada, os métodos assíncronos permitem lidar com várias tarefas simultaneamente, como processos em segundo plano ou processamento de entrada do usuário, sem congelar a thread da interface do usuário. Neste artigo, exploraremos como async e await funcionam juntos, cobrindo conceitos como métodos async void, manipulação de erros, trocas de contexto e contextos de sincronização. Você aprenderá como gerenciar com eficiência múltiplas operações assíncronas e lidar com exceções, garantindo uma execução tranquila em toda a thread de chamada, thread da interface do usuário e conclusão da tarefa. Essa abordagem é particularmente útil em cenários que envolvem sistemas de arquivos, solicitações de rede ou solicitações simultâneas, onde a capacidade de resposta é fundamental.
Tim introduz a programação assíncrona explicando a diferença entre operações síncronas e assíncronas. Na programação síncrona, as tarefas são executadas sequencialmente, causando atrasos se uma tarefa demorar muito. A programação assíncrona permite que as tarefas sejam executadas em paralelo ou em segundo plano, melhorando o desempenho e a capacidade de resposta.
Benefícios da Programação Assíncrona
Tim destaca dois benefícios principais da programação assíncrona:
-
Melhoria na capacidade de resposta da interface do usuário (IU) : Ao executar tarefas de forma assíncrona, a IU permanece responsiva mesmo durante a execução de operações demoradas.
- Execução paralela : Tarefas independentes podem ser executadas em paralelo, reduzindo o tempo total necessário para concluí-las.
Demonstração do Aplicativo - Passo a Passo
Tim configura um aplicativo de demonstração do Windows Presentation Foundation (WPF) para demonstrar a diferença entre operações síncronas e assíncronas. O aplicativo possui dois botões: um para executar tarefas de forma síncrona e outro para executá-las de forma assíncrona.

O aplicativo também inclui um painel de resultados para exibir a saída.
Operação síncrona
Tim explica o código por trás da operação síncrona. Um Stopwatch é usado para medir o tempo de execução da tarefa.
private void ExecuteSync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
List<string> websites = GetWebsiteList();
foreach (var website in websites)
{
string result = DownloadWebsite(website);
ReportWebsiteInfo(website, result);
}
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
private List<string> GetWebsiteList()
{
return new List<string>
{
"https://www.yahoo.com",
"https://www.google.com",
"https://www.microsoft.com",
"https://www.cnn.com",
"https://www.codeproject.com",
"https://www.stackoverflow.com"
};
}
private string DownloadWebsite(string websiteURL)
{
WebClient client = new WebClient();
return client.DownloadString(websiteURL);
}
private void ReportWebsiteInfo(string website, string data)
{
ResultsWindow.Text += $"{website}: {data.Length} characters{Environment.NewLine}";
}
private void ExecuteSync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
List<string> websites = GetWebsiteList();
foreach (var website in websites)
{
string result = DownloadWebsite(website);
ReportWebsiteInfo(website, result);
}
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
private List<string> GetWebsiteList()
{
return new List<string>
{
"https://www.yahoo.com",
"https://www.google.com",
"https://www.microsoft.com",
"https://www.cnn.com",
"https://www.codeproject.com",
"https://www.stackoverflow.com"
};
}
private string DownloadWebsite(string websiteURL)
{
WebClient client = new WebClient();
return client.DownloadString(websiteURL);
}
private void ReportWebsiteInfo(string website, string data)
{
ResultsWindow.Text += $"{website}: {data.Length} characters{Environment.NewLine}";
}
Este código executa os seguintes passos:
- Inicie o cronômetro : mede o tempo de execução.
- Obter a lista de sites : Recupera uma lista de URLs de sites.
- Baixar cada site : Faz o download do conteúdo de cada site.
- Informações do site do relatório : Exibe o URL e o tamanho do conteúdo baixado.
- Parar o cronômetro : Interrompe o temporizador e exibe o tempo total de execução.
Observando a operação síncrona
Tim executa o aplicativo para demonstrar a operação síncrona. Ele observa que a interface do usuário deixa de responder enquanto os sites estão sendo baixados e que os resultados são exibidos todos de uma vez após a conclusão dos downloads.

Para uma demonstração prática da forma imóvel, assista ao vídeo para melhor compreensão a partir de 9:20.
Criando uma tarefa assíncrona
Tim resolve o primeiro problema convertendo o método síncrono em um método assíncrono. Isso envolve o uso das palavras-chave async e await. Segue uma explicação passo a passo do processo:
-
Copiar método síncrono existente : Tim copia o método síncrono existente e renomeia-o para indicar que ele será assíncrono.
private async Task RunDownloadAsync() { // Same code as RunDownloadSync, but will be modified for async }private async Task RunDownloadAsync() { // Same code as RunDownloadSync, but will be modified for async } -
Modificar a chamada de download para execução assíncrona :
- Tim envolve a chamada para download em um
Task.Runpara executá-la de forma assíncrona.
private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { return await Task.Run(() => DownloadWebsite(websiteURL)); }private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { return await Task.Run(() => DownloadWebsite(websiteURL)); }- A palavra-chave
awaitgarante que o método espere que a tarefa assíncrona seja concluída antes de continuar.
- Tim envolve a chamada para download em um
-
Certifique-se de que o método seja assíncrono :
- A assinatura do método é atualizada para incluir a palavra-chave
async, e retorna uma Task.
private async Task RunDownloadAsync() { List<string> websites = GetWebsiteList(); foreach (var website in websites) { var result = await DownloadWebsiteAsync(website); ReportWebsiteInfo(website, result); } }private async Task RunDownloadAsync() { List<string> websites = GetWebsiteList(); foreach (var website in websites) { var result = await DownloadWebsiteAsync(website); ReportWebsiteInfo(website, result); } } - A assinatura do método é atualizada para incluir a palavra-chave
-
Lide com o evento corretamente :
- O manipulador de eventos de clique do botão foi atualizado para chamar o novo método assíncrono.
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e) { Stopwatch stopwatch = Stopwatch.StartNew(); await RunDownloadAsync(); stopwatch.Stop(); ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}"; }private async void ExecuteAsync_Click(object sender, RoutedEventArgs e) { Stopwatch stopwatch = Stopwatch.StartNew(); await RunDownloadAsync(); stopwatch.Stop(); ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}"; }Observe que os manipuladores de eventos podem retornar void mesmo que chamem métodos assíncronos.
Abordando a capacidade de resposta da interface do usuário
Tim mostra que, usando a palavra-chave await, a interface do usuário permanece responsiva. Isso permite que os usuários interajam com a janela enquanto a tarefa assíncrona está em execução.
// UI remains responsive
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
// UI remains responsive
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
Garantir a sincronização correta
Para garantir que o tempo total de execução seja relatado corretamente, Tim adiciona a palavra-chave await à chamada no manipulador de eventos de clique do botão.
// Correctly waits for the asynchronous task to complete
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
// Correctly waits for the asynchronous task to complete
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
Ao aguardar a conclusão do método assíncrono, o tempo de execução é medido com precisão e os resultados são exibidos à medida que são obtidos.
Criando processos assíncronos paralelos
Tim aborda a limitação de esperar que cada tarefa seja concluída sequencialmente, utilizando a execução paralela. Eis como ele modifica o código para conseguir isso:
-
Copie o método assíncrono existente : Tim duplica o método assíncrono existente e o renomeia para indicar execução paralela.
private async Task RunDownloadParallelAsync() { // Parallel execution logic will be added here }private async Task RunDownloadParallelAsync() { // Parallel execution logic will be added here } -
Criar uma lista de tarefas :
- É criada uma lista de tarefas para armazenar todas as tarefas de download.
List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>(); -
Iniciar todas as tarefas sem esperar : Em vez de aguardar cada tarefa de download imediatamente, Tim as adiciona à lista de tarefas.
foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); }foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); } -
Aguarde a conclusão de todas as tarefas :
- O método
Task.WhenAllé usado para aguardar a conclusão de todas as tarefas. Este método retorna uma matriz de resultados assim que todas as tarefas forem concluídas.
WebsiteDataModel[] results = await Task.WhenAll(tasks);WebsiteDataModel[] results = await Task.WhenAll(tasks); - O método
-
Processar os resultados : Após a conclusão de todas as tarefas, Tim processa os resultados em um loop.
foreach (var result in results) { ReportWebsiteInfo(result); }foreach (var result in results) { ReportWebsiteInfo(result); }
Segue o código completo para o método de execução paralela:
private async Task RunDownloadParallelAsync()
{
List<string> websites = GetWebsiteList();
List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
foreach (var website in websites)
{
tasks.Add(DownloadWebsiteAsync(website));
}
WebsiteDataModel[] results = await Task.WhenAll(tasks);
foreach (var result in results)
{
ReportWebsiteInfo(result);
}
}
private async Task RunDownloadParallelAsync()
{
List<string> websites = GetWebsiteList();
List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
foreach (var website in websites)
{
tasks.Add(DownloadWebsiteAsync(website));
}
WebsiteDataModel[] results = await Task.WhenAll(tasks);
foreach (var result in results)
{
ReportWebsiteInfo(result);
}
}
Atualizando o manipulador de eventos
Tim atualiza o manipulador de eventos de clique do botão para chamar o novo método de execução paralela.
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadParallelAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await RunDownloadParallelAsync();
stopwatch.Stop();
ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
Aguardando RunDownloadParallelAsync, o tempo total de execução é medido e relatado com precisão.
Observando a melhoria do desempenho
Tim demonstra a melhoria de desempenho executando o aplicativo em paralelo. Os resultados mostram uma redução significativa no tempo total de execução em comparação com a execução sequencial.
// Parallel execution
async Task RunDownloadParallelAsync();
// Parallel execution
async Task RunDownloadParallelAsync();
O aumento de velocidade é evidente, pois os sites são baixados simultaneamente, reduzindo o tempo total de espera à duração do download individual mais lento.
Envolvendo Método em Task.Run() vs Chamada de Método Assíncrono
Tim explica o conceito de usar Task.Run() para envolver um método síncrono e torná-lo assíncrono quando você não pode modificar o método original. No entanto, ele também mostra a abordagem preferida de modificar o próprio método para ser assíncrono, caso você tenha controle sobre ele.
-
Envolvendo Método em
Task.Run(): Essa abordagem é útil quando você não pode alterar o código do método original, mas ainda deseja executá-lo de forma assíncrona.private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { return await Task.Run(() => DownloadWebsite(websiteURL)); }private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { return await Task.Run(() => DownloadWebsite(websiteURL)); } -
Tornando um método assíncrono :
-
Se for possível modificar o método, é melhor convertê-lo para assíncrono usando uma API assíncrona apropriada.
- Tim demonstra isso mudando
DownloadWebsiteparaDownloadWebsiteAsynce usandoDownloadStringTaskAsyncdeWebClient.
private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { WebClient client = new WebClient(); string data = await client.DownloadStringTaskAsync(websiteURL); return new WebsiteDataModel { URL = websiteURL, Data = data }; }private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL) { WebClient client = new WebClient(); string data = await client.DownloadStringTaskAsync(websiteURL); return new WebsiteDataModel { URL = websiteURL, Data = data }; } -
-
Ajustando o método de chamada :
- Após converter o método, o código de chamada precisa ser ajustado para remover o envoltório
Task.Run()e chamar diretamente o método assíncrono.
private async Task RunDownloadParallelAsync() { List<string> websites = GetWebsiteList(); List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>(); foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); } WebsiteDataModel[] results = await Task.WhenAll(tasks); foreach (var result in results) { ReportWebsiteInfo(result); } }private async Task RunDownloadParallelAsync() { List<string> websites = GetWebsiteList(); List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>(); foreach (var website in websites) { tasks.Add(DownloadWebsiteAsync(website)); } WebsiteDataModel[] results = await Task.WhenAll(tasks); foreach (var result in results) { ReportWebsiteInfo(result); } } - Após converter o método, o código de chamada precisa ser ajustado para remover o envoltório
Resumo dos pontos principais
Tim conclui com vários pontos importantes sobre o uso de async e await:
-
Garantir Métodos Retornando Tarefas: Sempre que marcar um método como
async, ele deve retornar umTaskouTask<t>em vez de void (exceto para manipuladores de eventos). -
Use
awaitpara Operações Confiáveis: Use a palavra-chaveawaitquando precisar do resultado de uma operação assíncrona antes de prosseguir. -
Envolvimento de Tarefas para Código Não Modificável: Use
Task.Run()para envolver métodos síncronos quando não se pode modificar o método original. -
Marque Métodos Assíncronos de Forma Apropriada: Sempre adicione
Asyncao nome do método para indicar que é uma operação assíncrona. - Execução paralela versus sequencial : Decida se as tarefas podem ser executadas em paralelo ou se devem esperar umas pelas outras, com base nas dependências entre elas.
Tim enfatiza que a programação assíncrona em C# foi simplificada com async, await e Task. Complexidades como contextos de threading e modelos de apartamento são gerenciadas nos bastidores.
Conclusão
O tutorial de Tim Corey sobre async e await em C# oferece um recurso inestimável para dominar a programação assíncrona. Ao explicar cuidadosamente conceitos como paralelismo de tarefas, responsividade da interface do usuário e a importância de usar async e await no contexto correto, Tim fornece aos desenvolvedores as ferramentas para criar aplicativos mais rápidos e responsivos. Seu passo a passo detalhado demonstra como lidar de forma eficiente com tarefas de longa duração sem bloquear a thread principal, além de estratégias para gerenciar operações assíncronas e execução paralela.
Para uma compreensão mais aprofundada e exemplos práticos adicionais, recomendo vivamente que veja o vídeo de Tim Corey. Suas ideias sobre métodos assíncronos e vazios, tratamento de erros e gerenciamento de contextos de sincronização aprimorarão ainda mais sua capacidade de implementar esses conceitos em seus projetos.
