Existem diversos cenários em que uma requisição pode demorar para ser processada, por exemplo em chamadas que aguardam algum retorno de um webservice, ou em chamadas que exigem consultas demoradas a base de dados ou em aplicações real-time, que ficam aguardando alguma mudança para retornar a requisição (ex: chat).
No IIS, o .NET mantém um Pool de Threads (numero limitado de Threads) que são utilizadas para processar as requisições que chegam. Quando uma requisição chega, uma Thread deste Pool é utilizada para processar a requisição. Se esta requisição é processado de uma maneira síncrona, a Thread que processa a requisição é bloqueada enquanto a solicitação é processada, e essa Thread não pode atender a outra requisição durante esse período.
Em cenários de baixa concorrência, ou de requisições simples isso pode não ser um problema, porque o pool de threads pode ser grande o suficiente para acomodar muitas requisições bloqueados. Entretanto, como o número de threads no pool é limitado, em condições de muitos acessos, grande concorrência, e ou requisições demoras, acontece um enfileiramento destas requisições. Quando essa fila se torna muito grande, o servidor Web começa a rejeitar pedidos com um status HTTP 503 (Server Too Busy).
Processando Requisições Assíncronas
Em situações onde este enfileiramento pode acontecer é possível tratar de maneira assíncrona as requisições que levam mais tempo para serem processadas. Lembrando que uma requisição assíncrona demora a mesma quantidade de tempo para processar do que uma requisição tratada de maneira síncrona. Exemplo, se uma requisição faz uma chamada para um webservice e demora 1 segundo pata completar, a requisição leva 1 segundo se ele for realizada de forma síncrona ou assíncrona. Porem, durante uma requisição assíncrona, aquela Thread do Pool não é bloqueada para responder a outros pedidos enquanto aguarda o primeiro pedido para ser concluído.
Portanto, solicitações assíncronas evitam filas quando há muitas requisições que possuem um longo tempo de duração.
Como Acontece?
Quando uma ação assíncrona é invocada, ocorrem as seguintes etapas:
O IIS recebe uma Thread do Pool de Threads (worker thread pool) e designa ela para lidar com uma solicitação de entrada. Esta work thread inicia a operação assíncrona.
A worker thread é devolvida ao pool de threads para atender outra requisição Web.
Quando a operação assíncrona for concluída, ele notifica ASP.NET.
O IIS recebe uma worker thread do pool de threads (que pode ser um thread diferente do thread que iniciou a operação assíncrona) para processar o restante do pedido, incluindo o processamento da resposta.
Como escolher entre Controllers Síncronos e Assíncronos?
Cada cenário possui diversas características unicas que influenciam na escolha entre a abordagem síncrona e a assíncrona, entretanto podemos desenhar algumas características para criar as principais recomendações:
Síncrono: use a abordagem sincrona quando:
- as operações são simples e de curta duração;
- simplicidade é mais importante do que eficiência;
- as operações são basicamente de CPU ao invés de utilizarem muito disco ou rede. métodos assincronos em operações do tipo CPU-bound não oferecem resultados melhore
- as operações são rede-bound ou I / O-bound em vez de CPU-bound (leitura de arquivos, chamadas de webservices, etc);
- teste mostram que as operações de bloqueio são um gargalo no desempenho do site e que o IIS pode atender mais solicitações, utilizando métodos de ação assíncrona para estas chamadas de bloqueio;
- paralelismo é mais importante do que a simplicidade do código;
- você quer fornecer um mecanismo que permite aos usuários cancelar um pedido de longa duração;
Após isso vou escolher iniciar com um exemplo de projeto ( Internet Application)
Isto nos cria a seguinte estrutura de projetos
Se analisarmos o Controller principal do projeto ( HomeController) veremos o seguinte codigo
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
}
a classe HomeController herda da classe abstrata Controller
public class HomeController : Controller
Para trabalharmos com um Controller Assíncrono, a primeira coisa que devemos fazer é com que a classe do Controller herde da classe abstrata AsyncController
public class HomeController : AsyncController
Após isso precisamos mudar a forma de abordar as Actions da classe, desacoplando o inicio dela em um método que retornará void e outro método que executará o processamento e retornará o esperado pelo cliente, alem disso devemos utilizar o padrão dos nomes dos métodos, no primeiro adicionaremos o final Async e no retorno o Completed
public class HomeController : AsyncController
{
public void IndexAsync()
{
//Inicio da requisição
}
public ActionResult IndexCompleted()
{
//Retorno
return View();
}
}
O ASP.NET controla a requisição assincrona com o AsyncManager, por isso precisamos utilizar o padrão nos nomes dos métodos, o AsyncManager funciona resumidamente como um gerenciador do estado da requisição, quando o contador é zerado ele aciona o método de retorno.
Criei uma classe chamada Processamento, que tem um método chamado Processar, que demora 10000 milisegundos para retornar.
public class Processamento
{
public void Processar()
{
Thread.Sleep(10000);
return;
}
}
Agora no método IndexAsync eu farei 3 coisas, primeiro adicionarei um item ao contador no AsyncManager, após isso criarei meu objeto Processamento, e por último utilizando a classe Task (http://msdn.microsoft.com/en-us/library/dd235678.aspx) do .NET chamarei o método Processar de uma maneira assíncrona, nessa operação assíncrona, eu não posso esquecer de retirar um item do contador do AsyncManager quando o Processar terminar.
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment();
var processamento = new Processamento();
Task.Factory.StartNew(() =>
{
processamento.Processar();
AsyncManager.OutstandingOperations.Decrement();
});
}
Assim que retirarmos um Item do AsyncManager, ele retornará a requisição chamando o IndexCompleted
public ActionResult IndexCompleted()
{
return View();
}
Abaixo podemos ver como ficaram as classes que utilizei neste exemplo
public class HomeController : AsyncController
{
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment();
var processamento = new Processamento();
Task.Factory.StartNew(() =>
{
processamento.Processar();
AsyncManager.OutstandingOperations.Decrement();
});
}
public ActionResult IndexCompleted()
{
return View();
}
}
public class Processamento
{
public void Processar()
{
Thread.Sleep(10000);
return;
}
}
Exitem diversas maneira e funcionalidades que podemos explorar com o AsyncController, como por exemplo a passagem de parâmetros do método assíncrono para nosso método de retorno da requisição, para isso vou alterar meu método Processar para ele retornar um int
public class Processamento
{
public int Processar()
{
Thread.Sleep(10000);
return 2;
}
}
Agora vou utilizar o AsyncManager para passar o valor
Task.Factory.StartNew(() =>
{
var retorno =processamento.Processar();
AsyncManager.Parameters["quantidade"] = retorno;
AsyncManager.OutstandingOperations.Decrement();
});
Finalmente preciso alterar meu método de retorno o IndexCompleted para ele receber o parâmetro, aproveitei e atribui ele a um ViewBag chamado Quantidade, para exibir na View.
public ActionResult IndexCompleted(int quantidade)
{
ViewBag.Quantidade = quantidade;
return View();
}
Bom, acho que o mais importante é sabermos identificar em nossos cenários qual abordagem utilizar, sendo que se precisarmos de uma abordagem assíncrona o ASP.NET MVC oferece um excelente suporte.
muito obrigado
Rodolfo





