Views com métodos assíncronos em uma aplicação ASP.NET MVC 6

Continuando a série que explora a utilização de programação assíncrona para melhorar o desempenho de uma aplicação ASP.NET MVC, nesse post mostrarei como utilizar uma nova funcionalidade presente no ASP.NET MVC 6, que é a possibilidade de utilizar chamadas assíncronas dentro de uma view utilizando o Razor, que é a engine de renderização de HTML utilizada pelo framework.

Para contextualizar esse exemplo, é recomendado ler os posts anteriores da série:

http://www.dotnetflash.com.br/2015/01/melhorando-a-performance-percebida-com-views-parciais-assincronas/

http://www.dotnetflash.com.br/2015/01/utilizando-chamadas-assincronas-no-servidor-em-uma-aplicacao-asp-net-mvc/

No segundo post da série, implementei chamadas assíncronas para os métodos mais lentos, que causavam a queda do desempenho da aplicação. Vou utilizar essa mesma aplicação como base para exemplificar o uso de chamadas assíncronas dentro de uma view Razor.

Relembrando os métodos assíncronos utilizados para retornar as informações pesadas da view:

Para simular os métodos lentos, introduzi um atraso de 1 segundo na execução do RecuperarContasAPagarRecentesAsync() e 2 segundos no RecuperarLogsRecentesAsync(), utilizando o método Delay(Int32) da classe Task. Os métodos assíncronos em uma aplicação .Net retornam um objeto do tipo Task<TResult> e é justamente isso que vocês podem ver no exemplo de código acima. Implementamos os métodos lentos do nosso repositório de modo que eles retornem tasks a serem executadas quando a informação for necessária. Observe que não estamos utilizando a palavra reservada await para recuperar o resultado dos métodos assíncronos, e sim repassando as tarefas para as propriedades dos modelos das views:

Com essa nova implementação, só vamos utilizar o await para executar o resultado dos métodos dentro do código da view, conforme código abaixo:

A nova sintaxe do Razor nos permite fazer chamadas a métodos assíncronos dentro do código da view.

O ganho de desempenho utilizando essa técnica é de 30%, conforme mostra a tabela abaixo.

ViewTempo total de renderização da página
Com chamadas síncronas4,52 segundos
Com chamadas assíncronas3,17 segundos
Ganho de desempenho com chamadas assíncronas30%

Esse ganho de desempenho ocorre, pois a chamada de um método assíncrono utilizando a palavra reservada await não bloqueia a thread principal da aplicação, liberando-a para continuar a execução. No nosso exemplo, a view chama primeiro o método RecuperarContasAPagarRecentesAsync() e continua executando, até chamar o método RecuperarLogsRecentesAsync(). Com isso, o tempo de resposta melhora, pois uma função não fica esperando a outra terminar de executar para iniciar o seu processamento.

O modelo de programação assíncrona é uma tendência forte no mundo .Net e vemos que a Microsoft está cada vez mais incorporando esse paradigma dentro do framework, com o objetivo de facilitar e incentivar o uso. Vale a pena investir o seu tempo para dominar esse tipo de técnica, pois desempenho é um fator vital para qualquer aplicação.

Para ver uma demonstração ao vivo da aplicação, clique no link: http://razorasyncmvc6.azurewebsites.net

SHOW ME THE CODE!!

https://github.com/marcellalves/asp-net-mvc-6-razor-async

Para ampliar o conhecimento sobre esse assunto, leia também:

http://www.rodolfofadino.com.br/2014/11/asp-net-mvc-6-razor-com-suporte-ao-async-e-flush/

http://blog.stephencleary.com/2012/02/async-and-await.html (em inglês)

Utilizando chamadas assíncronas e paralelas para melhorar o desempenho de uma aplicação ASP.NET MVC

No post anterior, mostrei que o desempenho percebido pelo usuário pode ser melhorado através da renderização das views de forma assíncrona. Esse é um recurso de cliente muito útil e possui diversas aplicações, mas ele melhora muito pouco o tempo de resposta total da aplicação. Para conseguirmos um ganho real de desempenho, precisamos melhorar o tempo de resposta no lado do servidor, que é o principal ponto de lentidão da aplicação proposta. Para entender melhor o contexto desse artigo, sugiro a leitura do post anterior.

Um dos recursos que o .Net Framework oferece desde a versão 4.5 é a opção de trabalhar com métodos assíncronos, utilizando as palavras reservadas async e await. Esse tipo de função não bloqueia a thread principal do servidor web, que é rapidamente liberada para atender a outras requisições.  Quando termina de executar, esse tipo de método retorna um objeto do tipo Task<TResult>, que vai executar em uma thread que esteja disponível, podendo ser a thread que iniciou a execução ou não. Para maiores detalhes, sugiro a leitura desse artigo: http://www.asp.net/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4.

Esse tipo de chamada permite melhorar a escalabilidade de uma aplicação, tornando-a capaz de receber mais requisições de usuário, o que melhora o tempo de resposta da aplicação de forma geral.

Vamos alterar a aplicação apresentada no post anterior, transformado os métodos lentos do repositório em assíncronos.

O método  RecuperarTodasContasAPagar fica assim:

O método RecuperarTodosLogs fica assim:

Para chamar os novos métodos assíncronos, precisamos tornar a action do controller assíncrona. Observe que é necessário utilizar a palavra reservada await nas chamadas:

Testando o desempenho da versão assíncrona, percebemos que o tempo de resposta é praticamente o mesmo da versão síncrona:

Versão síncrona executou em 3010 milissegundos
Versão síncrona executou em 3010 milissegundos
Versão assíncrona executou em 3001 milissegundos
Versão assíncrona executou em 3001 milissegundos
ActionsTempo Execução em Milissegundos
Síncrona3010
Assíncrona3001
Ganho porcentual de performance0,30%

O ganho de performance obtido ao mudar os métodos lentos para assíncrono foi de menos de 1%. Podemos perceber, dessa forma, que chamadas assíncronas permitem um ganho de tempo de resposta para aplicações com muitos usuários simultâneos concorrendo por recursos do servidor. O nosso cenário é um pouco diferente, estamos focados em melhorar o tempo de resposta para cada usuário. A aplicação tem poucos acessos concorrentes e o uso de async/await não melhorou muito nesse cenário. Para conseguirmos um ganho mais expressivo de desempenho, precisamos utilizar um outro recurso oferecido pelo .Net Framework, que é a execução em paralelo de métodos síncronos. Essa funcionalidade é fornecida através da classe Parallel, com o seu método Invoke. Dessa forma, continuaremos utilizando os métodos originais, síncronos. O que muda é que agora eles executarão em paralelo na action da controller: Um ponto interessante é que o método Invoke da classe Parallel é um helper, que utiliza o método WaitAll da classe Task. Abaixo, um exemplo de como a chamada acima poderia ser escrita utilizando WaitAll:

O Elemar Jr escreveu uma série de posts sobre programação assíncrona e paralela utilizando C#, vale a pena conferir: http://elemarjr.net/2011/09/07/paralelismo-em-mtodos-sncronos/http://elemarjr.net/2011/08/13/classe-task-alguns-exemplos/.

Executando a aplicação com a action paralela, percebemos um ganho efetivo de desempenho em relação à versão original:

Utilizando chamadas paralelas, obtemos ganho real de desempenho.
Utilizando chamadas paralelas, obtemos ganho real de desempenho.
ActionsTempo Execução em Milissegundos
Síncrona3010
Assíncrona3001
Paralela2013
Ganho porcentual de performance33,12%

Utilizando métodos síncronos executados de forma paralela, conseguimos um ganho de desempenho de 33% no servidor.

A partir desses exemplos, podemos concluir que o .Net Framework apresenta diversas opções de melhoria de desempenho e escalabilidade de uma aplicação. Porém, é necessário conhecer os detalhes de cada uma delas para poder avaliar e decidir qual é a melhor opção para atingir os objetivos específicos que você procura para o seu caso de uso.

Demonstração funcional da aplicação:

Action com chamadas assíncronas: http://viewassincronasaspnetmvc.azurewebsites.net/Home/IndexAsync

Action com execução paralela: http://viewassincronasaspnetmvc.azurewebsites.net/Home/IndexParallel

Código da aplicação: https://github.com/marcellalves/viewsparciaisassincronasASPNETMVC