Veja neste o que acontece numa aplicação Web ao marcar uma Action como assíncrono. Entenda o que acontece por trás das cenas. Como operações sincronas e assincronas influenciam no IIS.

Behind the scene photographs on the documentary film set of iBike, the movie. http://bit.ly/ibike-movie

Na primeira parte deste artigo, descrevi uma conversa na qual participei. Vou descrever novamente para embasar a segunda parte:

  • Quando devo utilizar async?
  • De maneira geral o async é para quando há operações de IO.
  • Então o HTTP é assincrono.
  • Não, o HTTP é sincrono.
  • Se ele é sincrono, então por que ele responde várias requisições simultaneamente?
  • É o browser quem faz varias requests HTTP. O IIS é assincrono, por isso consegue gerenciar e responder todas.
  • Então se o IIS é assincrono, por que preciso colocar Task ou async Task na minha Controller?
  • Para aumentar a escalabilidade da tua aplicação.

O primeira parte do artigo explica o que acontece no IIS antes da Request chegar na aplicação. Este artigo aborda porque as requisições assincronas aumentam a escalabilidade de aplicações Web.

Coding

Programação Assíncrona

Programação assíncrona é escrever código que permite várias coisas acontecerem ao mesmo tempo. Sem bloquear ou esperar outras. Diferente da programação síncrona ande as coisas acontecem na ordem que está escrito.

Async no .NET

O async e await foi introduzido no .NET Framework 4.5. Por trás deles está um pattern conhecido como Task-based Asynchronous Pattern (TAP). O inicio e fim de uma requisição assíncrona no TAP são representadas por um único método.

Exemplo de código com async/await

Há também outros padrões, que eram recomendadas em versões anteriores do .NET. Como o Asynchronous Programming Model (APM) e Event-based Asynchronous Pattern (EAP). Atualmente a Microsoft recomenda utilizar o TAP pattern no .NET. Porém estudar estes outros modelos te ajudar escrever melhores aplicações Multithread.

Beneficios

No desenvolvimento de aplicações Desktop, Client Side. Como Windows Form, Mobile e até mesmo Frontend Web o principal benefício do async é manter a UI responsiva enquanto outras operações estão sendo executadas.

No entanto esse objetivo muda durante o desenvolvimento de aplicações Backend. Onde principal benefício do async é a escalabilidade.

Como o IIS processa as Requisições

O IIS cria uma Worker Process que roda uma aplicação ASP.NET dentro.

Processos síncronos

Considere que uma Request em processamento esteja aguardando a resposta de algum recurso externo. Nesse caso um banco de dados. Quando a Request chega, o ASP.NET pega uma Thread de sua Thread Pool e o atribui a essa Request. Como está escrito de forma síncrona ocorrerá um Thread Blocking. Isso significa que a Thread ficará bloqueada até que a chamada ao banco de dados retorne.

syncrequest

Assim que a Requests é processada e a resposta enviada ao Client. A Thread fica novamente disponível no Thread Pool.

Agora imagine que a aplicação cresceu em numero de usuários. E mais Requests estão sendo efetuadas na aplicação. Como as Thread são sincronas eventualmente pode ocorrer uma situação onde não haja mais Thread disponíveis no Thread Pool.

Numa situação dessas a Requests ficará aguardando na Queue do IIS. Podendo causar um HTTP Error 503 (Serviço Indisponivel) na aplicação.

threadpoolsync-1

Veja na animação acima que as duas Thread ficam ocupadas aguardando a resposta do Banco de Dados. Enquanto aguardam a resposta não fazem o uso de processamento da CPU. Apenas aguardam a resposta.

Quando uma Thread está executando uma operação de I/O o SO percebe que a Thread está aguardando essa resposta. Por esse motivo, o Windows pausa a Thread para que não use recursos da CPU. Porém, ao fazer isso, ele ainda está na memória e a Thread não pode ser usado para atender outras Requests. Como consequëncia, o IIS precisa associar novas Threads para as Requests subsequentes.

Processo assíncrono

Código assíncrono resolve o problema de Thread Blocking. Faz isso liberando a Thread até que a operação de I/O seja concluida. Assim você recebe um objeto Task que representa o resultado da operação assíncrona. Quando a operação de I/O termina, o Windows notifica o Runtime e a continuação da Task é agendada no Thread Pool para voltar a ser processada.

Ao marcar um método como async, o compilador gera uma máquina de estado (TAP) em segundo plano. É um código extra. Mas se escrever um código assíncrono com sabedoria e estável, o overhead necessário para criar essa estrutura será compensado com os benefícios da execução assíncrona.

asyncexample-1

Em contraste ao exemplo anterior, com execução assíncrona o Servidor poderia lidar com mais Requests, sem que estas fiquem esperando na Queue do IIS. Isso só é possivel pois as Threads são liberadas para a Thread Pool enquanto a Request esta aguardando a resposta do I/O. Assim é possivel atender a um numero maior de requisições com o mesmo número de Threads.

asyncbenefits

Quando não utilizar async

O async não é uma bala de prata que pode resolver todos os problemas de performance. Veja abaixo casos que o async não resolve o problema, e pior, pode piorar em alguns cenários.

CPU-bound requests

Uma request que apenas faz uso de CPU, necessariamente faz uso de uma Thread para executar. Em uma request que faz uso de I/O é o Windows que sinaliza e tira a prioridade da Thread até que tenha uma resposta.

Ao ouvir async e await, no .NET, relacione diretamente a I/O. Por isso não há vantagens em requisições que usam apenas a CPU. Pelo contrário, adiciona um Overhead de código de forma desnecessária.

Problemas no Banco de dados

Se o gargalo de performance é o Banco de Dados esta demorando para responder, utilizar async não ajuda em nada as coisas. Dependendo do cenário pode piorar, pois o Server irá atender a mais requests simultaneamente e sobrecarregar ainda mais o Banco.

SHOW ME THE CODE

Após duas partes de muito teoria, chegou a hora do show. Provar o que esses dois artigos vem falando.

Pré-requisitos

  • VisualStudio 2019
  • .NET Core 2.2

Crie um novo projeto no VS do tipo Web. (Create New Project > ASP.NET Core Web Application)
Selecione o tipo API.

Abra o arquivo Program.cs e altere o Main. Será configurado o Thread Pool da aplicação para valores baixos. Assim é possivel testar com facilidade.

Altere também o Startup.cs, removendo código desnecessário:

Agora na Controller Values, será criado dois métodos. Um sync e outro async.

A api está pronta.

Testando

Para testar, adicione um novo projeto do tipo Console Application.

Abra o arquivo Program.cs e altere:

Tome atenção para o URL da Api. Ela pode ser localizada em Properties > launchSettings.json do projeto API.

Testando, coloque o projeto API como Startup Project:
startupproject

Rode a aplicação, assim que estiver rodando. Inicie o Console Application em modo Debug:

debug

Se tudo deu certo o resultado será parecido:
consoleapp

Teste sincrono

Na primeira linha do Console, foi feita 8 Requests simultaneas. Ocupando todas Threads do Thread Pool. Como esperado, cada uma demorou 5 segundos para responder.

Na segunda linha, 9 requests foram efetuadas. 8 atendidas pelas Threads. Porém a 9º chamada ficou na fila. Pois esgotaram as Threads. Após 5 segundos, houve uma Thread livre que foi atribuida para atender a 9 requisição. Assim o tempo total esperado era no minimo 10 segundos.

threadpoolsync-1

Teste async

Na primeira linha, foi feito um teste exatamente igual. 8 chamadas. Como esperado, todas atendidas e retornando em 5 segundos.

No segundo teste, a carga foi aumentada para 200 chamadas. Um numero bem superior. Apesar da Thread Pool ter apenas 8 Threads. Todas foram respondidas em 7 segundos. Pois as Threads eram liberadas enquanto havia um await em andamento, assim atendendo as demais da fila.

asyncbenefits

Download

O código do projeto está disponível no meu GitHub

Conclusão

Esse artigo tem como foco o processamento assíncrono do ASP.NET. O objetivo foi construir o conhecimento a partir do começo: Quando a request é feita lá no browser do cliente, passa pelo IIS e chega no ASP.NET.

E por fim, o maior beneficio de async no ASP.NET é escalabilidade!

Espero que você tenha gostado. E mais importante que isso, aprendido um pouco mais.

Referências