Desenvolvimento - ASP. NET

Programação Assíncrona no ASP.NET MVC

A Microsoft introduziu na versão 2.0 do ASP.NET WebForms uma funcionalidade chamada de páginas assíncronas, assunto qual já comentei bastante por aqui, e também fiz uma palestra no TechEd 2008 à respeito desse mesmo assunto.

por Israel Aéce



A Microsoft introduziu na versão 2.0 do ASP.NET WebForms uma funcionalidade chamada de páginas assíncronas, assunto qual já comentei bastante por aqui, e também fiz uma palestra no TechEd 2008 à respeito desse mesmo assunto. Para recapitular, e resumidamente falando, quando uma requisição chega para o IIS, o mesmo entrega para o ThreadPool da CLR, que utilizará uma thread para efetivamente executar aquela requisição.

Por padrão, as páginas são sempre síncronas, ou seja, enquanto aquela requisição não for finalizada, a thread não será liberada. O problema disso é que muitas vezes, as páginas executam processos pesados, como por exemplo, acesso à serviços, consultasem base de dados, etc. Essas tarefas são consideradas I/O-bound, ou seja, são tarefas que não dependem da máquina local, mas sim do processamento do computador remoto que hospeda o serviço/banco de dados, da latência da rede, etc. Neste tempo que a thread fica aguardando esse processo de I/O finalizar, ela poderia estar servindo outras requisições, também ASP.NET, mas que não fazem necessariamente acesso à recursos deste tipo, como por exemplo, páginas institucionais. Dependendo da demanda, é comumo usuárioreceber no navegador erros com as seguintes mensagens de erro: Server Unavaliable ou Server Too Busy (503).

As páginas assíncronas resolvem esse tipo de problema, ou seja, quando encontrar uma tarefa deste tipo, a executam em uma thread de I/O, devolvendo a thread para o ThreadPool, e dando a chance dela atender outras requisições. Quando o processo remoto finalizar, a thread de I/O é retornada com o resultado e, novamente, uma thread é apanhada do ThreadPool para finalizar a requisição, e que na maioria das vezes, irá renderizar o resultado.

Por ser algo que aumenta consideravelmente a performance, a Microsoft está introduzindo este mesmo recurso no ASP.NET MVC. A partir da versão 2.0 do mesmo, teremos a possibilidade de criar ações assíncronas. Aplicações MVC que estão sujeitas à uma grande quantidade de requisições, podem fazer as execuções das ações de forma assíncrona, trabalhando de forma bem semelhante ao WebForms, e dando também ao MVC o mesmo benefício.

O primeiro passo para a criação de ações assíncronas, é fazer com o controller herde da classe abstrata AsyncController e não apenas de Controller. Essa classe fornecerá toda a infraestrutura para que as ações sejam executadas assincronamente, mas é importante dizer que mesmo que o controller herde de AsyncController, ele ainda pode continuar executando ações síncronas. Ao contrário do modelo síncrono, ações assíncronas dependem de um par de métodos, sendo um que inicia a tarefa custosa, e o segundo que será disparado quando o processo for finalizado. Atualmente é necessário sufixar o nome da ação com os respectivos sufixos: "Async" e "Completed". Abaixo temos um exemplo de como fica a estrutura de um controller assíncrono:

publicclass UsuariosController : AsyncController
{
public void ListagemAsync(int quantidade)
{
//...
}

public ActionResult ListagemCompleted(Usuario[] usuarios)
{
//...
}
}

Repare que o primeiro método é definido como void, pois quem retorna o resultado para a View é o método que é disparado quando o processofor finalizado. Um outro detalhe importante é que o método sempre será referenciado ou acessado no navegador como "Listagem"e nunca como "ListagemAsync", pois os sufixos são somentes utilizados pelo runtime do ASP.NET.

Ao herdar da classe AsyncController, novas propriedades estão a nossa disposição, e uma delas é a AsyncManager, que retorna a instância de uma classe com o mesmo nome. Como o próprio nome diz, ela é a responsável por gerenciar as operações assíncronas. Essa classe fornece três propriedades: OutstandingOperations, Parameters e Timeout. A primeira delas, OutstandingOperations, retorna a instância de uma classe chamada OperationCounter, onde essa classe controla a quantidade de operações que foram inicializadas pela respectiva ação. Já a propriedade Parameters, serve como um dicionário de dados, que permite passar informações para o método de finalização (como o resultado, por exemplo) e, finalmente, a propriedade Timeout, onde podemos definir um número inteiro que representa a quantidade de milisegundos (padrão de 45000 (45 segundos)) que o ASP.NET irá aguardar até que a operação seja finalizado.

Como sabemos, o .NET Framework fornece duas formas para trabalho assíncrono: modelo APM (métodos Begin/End) ou o modelo de eventos. O MVC suporta as duas formas de trabalho, mas a implementação para cada uma delas é ligeiramente diferente. Independentemente de qual técnica você utilize para invocar, o método que é disparado quando o processo assíncrono é finalizado não mudará em nada. Abaixo temos a sua implementação, e podemos notar que ele nada sabe sobre questões assíncronas.

public ActionResult ListagemCompleted(Usuario[] usuarios)
{
ViewData["Usuarios"] = usuarios;
return this.View();
}

O próximo passo é codificar o método ListagemAsync, e vamos utilizar inicialmente o modelo de programação assíncrona do .NET (APM). Como exemplo, vamos consumir um serviço WCF que foi referenciado na aplicação. Lembre-se que ao referenciar um serviço WCF, por padrão, ele não traz as versões assíncronas das operações; para poder habilitá-las, consulte este artigo.

Vamos então instanciar o proxy para estabelecer o canal de comunicação entre a aplicação e o serviço WCF. Podemos notar que temos que obrigatoriamente invocar os métodos Increment e Decrement, expostos pela propriedade OutstandingOperations, para especificar a quantidade de operações assíncronas que estão em andamento. Depois disso, devemos inicializar a operação assíncrona, através do método BeginRecuperarUsuarios. De acordo com o modelo APM, além dos parâmetros exigidos pelo método em si, temos que informar um callback, que nada mais é do que o método que será executado quando o processo assíncrono for finalizado.

Note no código abaixo que dentro do método de callback, estamos recuperando o resultado (EndRecuperarUsuarios) e armazenando dentro da propriedade Parameters a coleção de usuários. O valor colocado dentro deste dicionário será passado para o método ListagemCompleted, através do parâmetro "usuarios". Em seguida estamos também decrementando o contador de operações assíncronas. Note que tudo o que foi descrito neste parágrafo, está sendo executado dentro do método chamadoSync, também fornecido pela propriedade AsyncManager. Isso é necessário para garantir que este código e, um pouco mais tarde, a execução do método ListagemCompleted, sejam disparados em uma thread que o ASP.NET terá o controle. Se você não se atentar à isso e tentar executar esse código, ainda estará em uma thread de I/O, fazendo com que o contexto do HTTP (HttpContext.Current) esteja nulo e, consequentemente, não conseguirá acessar grande parte dos recursos que precisa para exibir o resultado.

public void ListagemAsync(int quantidade)
{
ServicoDeUsuarios proxy =new ServicoDeUsuarios();
this.AsyncManager.OutstandingOperations.Increment();

proxy.BeginRecuperarUsuarios(quantidade, ar =>
{
AsyncManager.Sync(() =>
{
this.AsyncManager.Parameters["usuarios"] = proxy.EndRecuperarUsuarios(ar);
this.AsyncManager.OutstandingOperations.Decrement();
});
}, null);
}

Depois de visualizar a implementação baseada no modelo APM, temos agora o modelo de eventos. Neste caso não precisamos envolver o método Sync, pois o evento que determina que o processo foi finalizado já acontece dentro da thread do próprio ASP.NET. Tudo o que precisamos fazer é se vincular à este evento, e dentro dele armazenar o resultado na propriedade Parameters e decrementar o contador, tudo de forma bem parecida ao que vimos acima. Apenas para iniciar o processo assíncrono, você deverá invocar a versão assíncrona do método que é gerado durante a criação do proxy do WCF, que terá sempre o nome da operação sufixada com a palavra "Async".

public void ListagemAsync(int quantidade)
{
ServicoDeUsuarios proxy = new ServicoDeUsuarios();
this.AsyncManager.OutstandingOperations.Increment();

proxy.RecuperarUsuariosCompleted += (sender, e) =>
{
this.AsyncManager.Parameters["usuarios"] = e.Result;
this.AsyncManager.OutstandingOperations.Decrement();
};

proxy.RecuperarUsuariosAsync(quantidade);
}

Observação: Qual dos dois modelos utilizar? Isso vai depender da API que está sendo chamada dentro do controller/ação assíncrono. Se ela suportar os dois modelos, então você pode escolher um deles. Mas há situações que não temos esse luxo, como por exemplo, quando queremos invocar uma consulta no SQL Server usando o ADO.NET tradicional, ou até mesmo ler o conteúdo de um arquivo no disco. Essas classes apenas fornece o modelo APM, com um par de métodos Begin/End.

A Microsoft ainda disponibilizou dois atributos: AsyncTimeoutAttribute e NoAsyncTimeoutAttribute. O primeiro deles disponibiliza uma propriedade chamada Duration, que como falamos acima, recebe a quantidade de milisegundos que determina o timeout. Já o segundo atributo deve ser aplicado quando você quer deixar isso indefinido. Independentemente de qual irá utilizar, eles devem ser aplicados sempre ao método que está sufixado com a palavra "Async", assim como vemos abaixo:

[AsyncTimeout(Duration = 30000)]
public void ListagemAsync(int quantidade)
{
//...
}

Conclusão: É importante dizer que esse modelo de programação, apesar de tornar o código um pouco mais ilegível e poluído, traz um grande benefício em termos de performance e escalabilidade. Não pense que a página aparecerá no navegador do usuário enquanto a ação é processada, e quando ela for finalizada, aparecerá os dados na tela. Em termos visuais, você ainda terá o mesmo resultado, ou seja, enquanto a ação não for finalizada o navegador ficará bloqueado até que a requisição como um todo seja concluída, mas esta funcionalidade irá "desafogar" o ASP.NET.
Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.