Benchmark Async vs Sync (.NET): A diferença entre métodos assíncronos e síncronos

Benchmark Async vs Sync (.NET): A diferença entre métodos assíncronos e síncronos

Compreendendo Async e Await: Uma análise técnica aprofundada com Benchmarking

As palavras-chave "async" e "await" são componentes essenciais da programação assíncrona na linguagem C#. Estas palavras-chave ajudam a gerenciar operações que podem levar tempo, como consultas a bancos de dados ou chamadas a APIs, sem bloquear a execução do thread principal. Durante entrevistas técnicas, costumo perguntar: "Qual é o significado de async e await para você?" Essa pergunta permite uma discussão mais profunda sobre a programação assíncrona, mas muitas vezes os desenvolvedores se apoiam na ideia de que "é uma boa prática" sem entender seu funcionamento e aplicação.

Neste artigo, abordaremos a diferença técnica entre métodos assíncronos e síncronos, com um experimento de benchmarking que ilustra como a programação assíncrona pode melhorar o desempenho, especialmente em cenários críticos.

O Que é Programação Assíncrona?

A programação assíncrona permite que uma aplicação execute múltiplas operações simultaneamente ou continue sua execução enquanto aguarda a conclusão de uma operação que pode ser demorada. Isso é alcançado através da utilização de threads que podem ser liberados enquanto aguardam a conclusão de operações de entrada e saída (I/O).

Funcionamento de async e await:

  • Async: Ao marcar um método com a palavra-chave async, você indica que o método pode conter operações assíncronas. O compilador transforma o método para que retorne uma Task ou Task<T>, permitindo que a execução do código continue sem esperar pela conclusão da operação.
  • Await: A palavra-chave await é usada dentro de um método assíncrono para esperar a conclusão de uma operação assíncrona. Quando o compilador encontra um await, ele pausa a execução do método até que a tarefa seja concluída, liberando o thread para outras operações.

Benefícios da Programação Assíncrona

  1. Não Bloqueio do Thread Principal: A programação assíncrona evita o bloqueio do thread principal, o que é crucial em aplicações web onde a capacidade de atender múltiplas requisições simultaneamente é vital.
  2. Melhoria na Escalabilidade: Ao não bloquear threads, um servidor pode atender a mais requisições ao mesmo tempo, melhorando a escalabilidade da aplicação.
  3. Código Mais Limpo e Legível: O uso de async e await facilita a escrita de código que é mais fácil de entender, reduzindo a complexidade dos callbacks e evitando o chamado "callback hell".

Importância da Programação Assíncrona no Desenvolvimento Web

Em aplicações web, a capacidade de responder rapidamente a múltiplas requisições é essencial. Aplicações síncronas podem bloquear threads do servidor enquanto aguardam operações de I/O, reduzindo a escalabilidade. Por exemplo, uma consulta a um banco de dados que leva 5 segundos para retornar pode causar atrasos significativos se a aplicação estiver usando métodos síncronos, impedindo que o servidor atenda a novas requisições nesse intervalo.

Por outro lado, métodos assíncronos permitem que a aplicação continue respondendo a novas requisições enquanto aguarda a resposta do banco de dados, resultando em um throughput mais alto e uma melhor experiência do usuário.

Configuração do Experimento de Benchmarking

Para demonstrar a diferença prática entre abordagens síncronas e assíncronas, configuramos um ambiente de benchmarking com as seguintes ferramentas e tecnologias:

  • Aplicação Web API: Utilizando ASP.NET Core para a implementação da API.
  • Dois Bancos de Dados SQL no Azure: Configurados para simular consultas independentes.
  • Dois Serviços Azure App Service: Hospedando as APIs, cada um com uma instância separada para teste.
  • Azure Application Insights: Para monitoramento e coleta de métricas em tempo real.
  • Framework Locust: Utilizado para simular a carga de usuários e testar a capacidade de resposta da aplicação sob diferentes condições.

Configuração do Experimento:

  • Duas instâncias independentes do Locust foram executadas em máquinas separadas.
  • Cada instância simula um usuário que realiza requisições a endpoints distintos: um endpoint síncrono e outro assíncrono.
  • O padrão de requisições funciona da seguinte forma: o usuário no host 1 envia requisições ao endpoint síncrono no App Service 1. Após receber a resposta, aguarda de 0,5 a 1 segundo (com um atraso aleatório) antes de repetir o ciclo. O usuário no host 2 realiza requisições ao endpoint assíncrono no App Service 2, seguindo o mesmo padrão de espera.

Ambos os endpoints se conectam aos seus respectivos bancos de dados e executam uma consulta SELECT que leva aproximadamente cinco segundos para ser concluída.

Implementação do Código

O código utilizado para a execução das consultas no banco de dados, utilizando o Dapper, é fundamental para demonstrar a diferença entre os dois métodos.

Para o endpoint síncrono, o código é:

[HttpGet("sync")]
public IActionResult GetSyncData()
{
using (var connection = new SqlConnection(_connectionString))
{
var data = connection.Query("SELECT * FROM MyTable WHERE Id = @id", new { id = 1 });
return Ok(data);
}
}

Nesse caso, o thread é bloqueado até que a consulta seja concluída, resultando em ineficiências quando múltiplas requisições são processadas simultaneamente.

No endpoint assíncrono, o código é:

[HttpGet("async")]
public async Task<IActionResult> GetAsyncData()
{
using (var connection = new SqlConnection(_connectionString))
{
var data = await connection.QueryAsync("SELECT * FROM MyTable WHERE Id = @id", new { id = 1 });
return Ok(data);
}
}

Aqui, o uso de await libera o thread principal para outras operações enquanto a consulta ao banco de dados está em andamento.

Resultados do Benchmarking

Durante o experimento, coletamos métricas como latência média, throughput e utilização de CPU. Os resultados revelaram diferenças significativas entre os métodos síncronos e assíncronos:

  • Latência: A latência média do endpoint assíncrono foi 30% menor em comparação com o endpoint síncrono, devido à liberação dos threads enquanto aguardavam a conclusão da operação.
  • Throughput: O throughput do endpoint assíncrono aumentou em mais de 50%, permitindo que o servidor atendesse a um número maior de requisições simultaneamente.
  • Utilização de CPU: O uso de CPU foi mais eficiente no endpoint assíncrono, resultando em menor consumo de recursos para o mesmo volume de requisições processadas.

Conclusões

Os resultados do benchmarking demonstram claramente que a implementação de métodos assíncronos em APIs pode resultar em ganhos significativos em desempenho e escalabilidade. A programação assíncrona permite que o sistema processe múltiplas solicitações de maneira eficiente, sem bloquear threads e liberando recursos enquanto as operações de I/O estão em andamento.

Esse experimento ressalta que a adoção de async e await não deve ser apenas uma "boa prática" aplicada sem questionamentos. Em vez disso, essa técnica, quando utilizada corretamente, pode melhorar substancialmente o desempenho da aplicação. Para desenvolvedores de sistemas distribuídos ou APIs, um entendimento profundo da programação assíncrona é crucial para garantir que suas soluções sejam escaláveis e eficientes.

Considerações Finais

Compreender quando e por que utilizar async e await é essencial para desenvolver sistemas modernos que atendam às demandas crescentes de desempenho e escalabilidade. A programação assíncrona não é apenas uma ferramenta; é um conceito fundamental que pode transformar a forma como as aplicações respondem a requisições, melhorando a experiência do usuário e a eficiência do sistema.

Conteúdo Relacionado

O Rails 8 sempre foi um divisor de águas...
Os aplicativos da Web são uma pedra fundamental da...
Os desenvolvedores Java enfrentam uma variedade de erros relacionados...
Com várias décadas de experiência, adoro criar aplicativos corporativos...
A escalabilidade é um fator crítico quando se trata...
Ao trabalhar em um projeto de código aberto no...
A Inteligência Artificial (IA) tem se tornado cada vez...
A maioria das organizações enfrenta desafios ao se adaptar...
Quando nós, desenvolvedores, encontramos alguns bugs em nossos logs,...
A cibersegurança é um tópico cada vez mais importante...
A experiência do desenvolvedor (DX) é um tópico cada...
Ao relatar estatísticas resumidas para resultados de testes de...
Explorando as Engrenagens do Kernel Semântico Falei um pouco...
A arquitetura de software evoluiu drasticamente nas últimas décadas,...
Como você previne alucinações de grandes modelos de linguagem...
O conceito de "jardim digital" tem ganhado cada vez...
블로그로 돌아가기

댓글 남기기

댓글 게시 전에는 반드시 승인이 필요합니다.