Ir para o conteúdo do rodapé
Iron Academy Logo
Aprenda C#
Aprenda C#

Outras categorias

Dominando C# Async e Await

Tim Corey
38m 57s

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:

  1. 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.

  2. 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.

Aplicação de Demonstração

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:

  1. Inicie o cronômetro : mede o tempo de execução.
  2. Obter a lista de sites : Recupera uma lista de URLs de sites.
  3. Baixar cada site : Faz o download do conteúdo de cada site.
  4. Informações do site do relatório : Exibe o URL e o tamanho do conteúdo baixado.
  5. 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.

Operação Síncrona

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:

  1. 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
    }
  2. Modificar a chamada de download para execução assíncrona :

    • Tim envolve a chamada para download em um Task.Run para 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 await garante que o método espere que a tarefa assíncrona seja concluída antes de continuar.
  3. 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);
       }
    }
  4. 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:

  1. 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
    }
  2. 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>>();
  3. 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));
    }
  4. 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);
  5. 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.

  1. 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));
    }
  2. 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 DownloadWebsite para DownloadWebsiteAsync e usando DownloadStringTaskAsync de WebClient.
    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 };
    }
  3. 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);
       }
    }

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 um Task ou Task<t> em vez de void (exceto para manipuladores de eventos).

  • Use await para Operações Confiáveis: Use a palavra-chave await quando 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 Async ao 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.

Hero Worlddot related to Dominando C# Async e Await
Hero Affiliate related to Dominando C# Async e Await

Ganhe mais compartilhando o que você ama.

Você cria conteúdo para desenvolvedores que trabalham com .NET, C#, Java, Python ou Node.js? Transforme sua expertise em renda extra!

Equipe de suporte de ferro

Estamos online 24 horas por dia, 5 dias por semana.
Bater papo
E-mail
Liga para mim