segunda-feira, 9 de julho de 2012

MVC4 - Async

Bom dia... Faz tempo que não posto alguma coisa. Enfim.

Estou vendo MVC4 agora. No momento, mexendo com a parte de chamadas assíncronas.

A primeira coisa é que queria construir algum exemplo simples, para entender melhor. Os que achei envolviam serviços e eu não queria criar um serviço para depois testar o que eu precisava.

Meu primeiro problema foi que o .NET 4.0 não conhece as keywords novas, async e await. Instalei o 4.5. Mas o Visual Studio 2010 não conhece o 4.5. Tentei até mudar no arquivo csproj em um editor de texto, mas não funcionou. Tive que instalar o VS2012 RC (http://www.microsoft.com/visualstudio/11/en-us). Se quer um conselho, não resolva instalar às 21h da sexta-feira.

Vamos à programação. Descobri que eu tinha que criar um método que chamasse qualquer outra coisa assíncrona. Pelo que li, já no 4.0 foram implementados métodos assíncronos no .NET. Tentei com chamada para a internet, mas retornava muito rápido.

O que resolvi usar foi leitura de um arquivo grande (precisava ser grande para testar o tempo, se realmente diminuiria). Meu computador é um core i5, 2,3 GHz, 4gB de ram, 64bit. Dois arquivos de mais ou menos 130mB foram usados.

Usei a classe StreamReader para ler, o método ReadToEndAsync. Para o método ser assíncrono, deve ser colocada a keyword async antes do tipo do método. Os métodos assíncronos que forem chamados dentro dele devem ser chamados com a keyword await antes, se for necessário aguardar o retorno deles para continuar.

Para o retorno do método, se usa a classe Task. Sozinha se for um void, como generic se houver algum tipo de retorno (passando o tipo do retorno real para a Task).

Meu método de leitura:
        private async Task<String> readAsync(String filename)
        {
            var reader = new StreamReader(filename);
            return await reader.ReadToEndAsync();
        }

Chegou a me passar pela cabeça: se ele chama o método assíncrono e fica esperando o retorno, qual a diferença de fazer isso ou chamar o convencional mesmo? Eis a resposta:

        var first = readAsync(firstFile);
        await readAsync(secondFile);
        await first;

Chamamos o primeiro, esperamos a execução do segundo, então esperamos a do primeiro (provavelmente ele já terá terminado). O primeiro fica executando ao mesmo tempo que o segundo. Uma economia de mais de 25% do tempo. Testei com 3 arquivos, cada qual em torno de 80mB. Quase 45% de ganho.

Problemas que esbarrei, para tomar cuidado:
  • Tentei fazer com três arquivos, dos maiores. Recebi uma bela OutOfMemoryException. Precisa de um pouco de cuidado, não pedir muita coisa ao mesmo tempo;
  • Tentei abrir o StreamReader com using*, mas ele fechou o arquivo antes de ler, por ser assíncrono. Então, cuidado quando faz dispose de objetos, sendo assíncrono você nunca sabe quando vai realmente usá-los, a menos que use await.
Quando for fazer as Actions, a regra é a mesma para o método (que é assíncrono, como os métodos que ele chama). Isso é cascateado a partir do momento de você cria o método: todos que o chamarem terão que ser assíncronos para usar await, e dali para cima permanece essa regra.

Meu ponto de partida foi este artigo: http://www.asp.net/whitepapers/mvc4-release-notes#_Toc303253813. A parte do timeout que cita não consegui fazer funcionar. Ele até retorna um erro de timeout, mas não redireciona para a view (nem trocando o tipo do erro para o que aparece na tela amarela) e executa o processo inteiro antes de retornar o timeout. Não me parece muito útil desse jeito.



* using (var reader = new StreamReader(filename))

Nenhum comentário:

Postar um comentário