Israel Aece

Software Developer

Cancelando Requisições do HttpClient

Como sabemos, a classe HttpClient é uma espécie de proxy para o consumo de serviços REST a partir de aplicações .NET. Durante a sua criação é possível realizar diversas configurações, e as requisições que saem e chegam, compartilham estas configurações, evitando que se faça isso a cada nova requisição/resposta que é enviada/processada.

Entre os diversos métodos que esta classe fornece, o mais completo, ou melhor, "de mais baixo nível", é o SendAsync, que onde podemos (e devemos) configurar completamente a mensagem, especificando desde a URL até o método HTTP que será utilizado. Como facilitador, existem alguns métodos que foram criados sobre o método SendAsync, e foram nomeados utilizandos os verbos do HTTP (GET (GetAsync), POST (PostAsync), etc.).

A classe HttpClient fornece um método chamado CancelPendingRequests, que como o próprio nome sugere, solicitará o cancelamento de todas as requisições que estão sendo realizadas pela respectiva instância do HttpClient. Mas é provável que queremos ter um controle individual das requisições e conseguirmos cancelar especificamente uma delas.

Felizmente todos os métodos que iniciam uma nova requisição possuem um parâmetro onde é permitido controlar e, principalmente, cancelar a execução da mesma. Eles fazem uso de uma classe chamada CancellationTokenSource, que faz parte do .NET Framework, e possibilita ao chamador informar ao runtime que deseja cancelar a requisição que está em andamento.

Para exemplificar o seu uso, vamos utilizar uma aplicação WPF para iniciará a requisição, e enquanto ela estiver sendo executada, vamos dar a possibilidade ao usuário dele conseguir cancelar a mesma. Note no código abaixo que criamos em nível da classe um membro chamado status, qual será o responsável por gerenciar um eventual cancelamento. No clique do botão Requisitar, estamos recorrendo ao método GetAsync (chamando-o de forma assíncrona) e passamos o CancellationTokenSource. É importante notar que temos que envolver este código em um bloco try/catch para interceptar a exceção do tipo TaskCanceledException, que será disparada quando o cancelamento for efetivamente realizado.

public partial class MainWindow : Window
{
    private CancellationTokenSource status;
    private HttpClient proxy = new HttpClient();

    private async void Requisitar_Click(object sender, RoutedEventArgs e)
    {
        Reinicializar();

        try
        {
            var resposta = await proxy.GetAsync(Url.Text, status.Token);

            if (resposta.IsSuccessStatusCode)
                Conteudo.Text = await resposta.Content.ReadAsStringAsync();
        }
        catch (TaskCanceledException)
        {
            Conteudo.Foreground = Brushes.Red;
            Conteudo.Text = "*** A REQUISIÇÃO FOI CANCELADA ***";
        }
    }

    private void Reinicializar()
    {
        Conteudo.Text = null;
        Conteudo.Foreground = Brushes.Black;

        status = new CancellationTokenSource();
    }

    private void Cancelar_Click(object sender, RoutedEventArgs e)
    {
        status.Cancel();
    }
}

Agora o cancelamento pode ser solicitado ao clicar no botão Cancelar, e através do método Cancel da classe CancellationTokenSource, o código para o qual passamos este token (que foi o GetAsync) identifica a solicitação e cancela a execução, disparando a exceção que falamos acima. As imagens abaixo ilustram tanto a requisição sendo executada com sucesso quanto um cancelamento sendo solicitado e acatado, abortando o processamento.



Cultura padrão para AppDomain

O idioma configurado na minha máquina (através do Painel de Controle) é o pt-BR. Ao rodar uma aplicação .NET, por padrão, a mesma é configurada com a cultura que está definida lá, e sendo assim, todas as threads que rodam na aplicação seguirão a mesma cultura. O código abaixo ilustra o código de exemplo:

static void Main(string[] args)
{
    var data = DateTime.Now;
    var valor = 1982.81M;

    ExibirNaMesmaThread(data, valor);
    ExibirEmOutraThread(data, valor);
}

static void ExibirNaMesmaThread(DateTime data, decimal valor)
{
    Console.WriteLine(data);
    Console.WriteLine("{0:C2}", valor);
}

static async void ExibirEmOutraThread(DateTime data, decimal valor)
{
    await Task.Delay(1000);

    Console.WriteLine(data);
    Console.WriteLine("{0:C2}", valor);
}

O resultado ao rodar este código será:

22/09/2013 16:33:34
R$ 1.982,81

22/09/2013 16:33:34
R$ 1.982,81

A data e o valor decimal estão no formato brasileiro, assim como era de se esperar. Em aplicações onde queremos determinar e/ou fixar a cultura padrão, recorremos à propriedade CurrentCulture da classe Thread para isso, e geralmente fazemos a configuração na inicialização da aplicação. Só que essa configuração somente é válida quando estamos em uma aplicação que trabalha apenas com uma única thread. Se desejarmos configurar a cultura explicitamente, fazemos o seguinte:

var cultura = new CultureInfo("en-US");

Thread.CurrentThread.CurrentCulture = cultura;
Thread.CurrentThread.CurrentUICulture = cultura;

Só que ao rodar a aplicação, para nossa surpresa, teremos:

9/22/2013 4:37:58 PM
$1,982.81

22/09/2013 16:37:58
R$ 1.982,81

A primeira exibição mostra os valores no padrão americano, pois está rodando sobre na mesma thread em que configuramos a cultura. Já o método que roda assincronamente e, consequentemente, em outra thread, continua exibindo os valores no formato brasileiro, pois a configuração que fizemos afetou somente a thread corrente, e Task que foi criada pelo .NET para executar o método ExibirEmOutraThread continua utilizando no formato brasileiro.

A partir do .NET Framework 4.5, a classe CultureInfo passa a ter duas novas propriedades estáticas chamadas: DefaultThreadCurrentCulture e DefaultThreadCurrentUICulture, que determinam a cultura padrão para todas as threads que rodam dentro daquele AppDomain. Se optarmos por ela ao invés daquelas propriedades expostas pela classe Thread, teremos ambos os valores formatados igualmente para todos os métodos dentro da aplicação, independentemente de qual thread eles estejam rodando.

var cultura = new CultureInfo("en-US");

CultureInfo.DefaultThreadCurrentCulture = cultura;
CultureInfo.DefaultThreadCurrentUICulture = cultura;

9/22/2013 4:43:19 PM
$1,982.81

9/22/2013 4:43:19 PM
$1,982.81

Utilizando o DataReader Assincronamente

Na versão 2.0 do .NET Framework, a Microsoft incluiu uma série de novas funcionalidades em sua API de acesso a dados, o ADO.NET. Entre elas, podemos destacar o código genérico, MARS, Bulk-Copy e execução assíncrona de comandos e consultas.

O que tínhamos disponível naquela época é a implementação através do modelo assíncrono do .NET, que era implementado utilizando um par de métodos BeginXXX/EndXXX. Sendo assim, o método ExecuteReader passou a ter os métodos BeginExecuteReader e EndExecuteReader, enquanto o método ExecuteNonQuery, ganhou os métodos BeginExecuteNonQuery e EndExecuteNonQuery.

Da mesma forma, para ficar alinhado a nova forma de se trabalhar assincronamente nas linguagens, a execução assíncrona de comandos e consultas no ADO.NET 4.5 sofreu algumas mudanças, para seguir o modelo baseado em Tasks. Além das mudanças em nível das interfaces das classes, um detalhe importante é que não é mais necessário definir o flag Asynchronous Processing para True no arquivo de configuração, algo que sempre era descoberto somente depois que a aplicação estava em execução.

Para iniciar, a classe que representa a conexão (SqlConnection/DbConnection) fornecem a versão assíncrona do método Open, que é o OpenAsync. Este método retorna uma Task, o que a torna "aguardável", e com isso, podemos utilizar a keyword await para que a abertura possa ser realizada de forma assíncrona. Abaixo o código ilustra o uso deste método:

private async static Task Executar()
{
    using (var conn = new SqlConnection("..."))
    {
        await conn.OpenAsync();

        //...
    }
}

Como já era de se esperar, os mesmos métodos fornecidos na versão 2.0 do ADO.NET para processamento assíncrono, ganharam na versão baseada em Tasks na versão 4.5. No caso do ExecuteReader, temos o ExecuteReaderAsync. Já para o método ExecuteNonQuery, temos o ExecuteNonQueryAsync e, finalmente, para o ExecuteScalar, existe o ExecuteScalarAsync.

Todos estes métodos tratam-se da nova versão assíncrona, que basicamente retornam um objeto do tipo Task, que representa a tarefa que está sendo executada assincronamente. E, qualquer exceção que eventualmente ocorra dentro do processo assíncrono, ela será retornada/acessada através da Task que foi retornada pelo método. Abaixo temos um exemplo de como ler os dados através de DataReader, utilizando este novo modelo assíncrono:

private async static Task Executar()
{
    using (var conn = new SqlConnection("..."))
    {
        await conn.OpenAsync();

        using (var cmd = new SqlCommand("SELECT * FROM Cliente", conn))
            using (var dr = await cmd.ExecuteReaderAsync())
                while (await dr.ReadAsync())
                    if (!await dr.IsDBNullAsync(1))
                        Console.WriteLine(dr.GetString(1));
    }
}

Acima utilizamos o método ExecuteReaderAsync, mas ao percorrer o result-set retornado, utilizamos o - também novo - método ReaderAsync, que é a versão assíncrona, também baseada em Task,  do método Read do DataReader. Esse método em conjunto os métodos NextResultAsync, IsDBNullAsync e GetFieldValueAsync<T>, fornecem um controle muito mais refinado aos dados que estão sendo extraídos, pois quando olhamos um pouco mais de perto os internals de cada um deles, percebemos que a versão síncrona pode custar caro, prejudicando assim a escalabilidade.

Além disso, todos os métodos que vimos até aqui, possuem um segundo overload que suporta o cancelamento da tarefa custosa que está sendo executada. Para controlar o cancelamento, eles fazem uso da estrutura CancellationToken, e que podemos criar e informar ao invocar o método. Com uma pequena mudança na assinatura do método de exemplo que criamos acima (Executar), ele passará a receber o token que controla e propaga a notificação de cancelamento. Uma vez que o mesmo é repassado às tarefas que são executadas internamente, periodicamente o token é consultado para ver se o cancelamento foi ou não solicitado. A mudança é ligeira:

private async static Task Executar(CancellationToken ct)
{
    using (var conn = new SqlConnection("..."))
    {
        await conn.OpenAsync(ct);

        using (var cmd = new SqlCommand("SELECT * FROM Cliente", conn))
            using (var dr = await cmd.ExecuteReaderAsync(ct))
                while (await dr.ReadAsync(ct))
                    if (!await dr.IsDBNullAsync(1, ct))
                        Console.WriteLine(dr.GetString(1));
    }
}

Como percebemos, para preparar o método para permitir o cancelamento, é receber no parâmetro um CancellationToken, e propagá-lo para os métodos internos. Abaixo, estamos consumindo o método Executar que criamos, só que agora estamos passando um token que que será cancelado em dois segundos. Se depois deste tempo o método não concluir, uma exceção será disparada, informando que a tarefa foi cancelada.

var cts = new CancellationTokenSource();

try
{
    cts.CancelAfter(TimeSpan.FromSeconds(2));
    Executar(cts.Token).Wait();
}
catch (Exception ex)
{
    //trata exceção
}

Contratos Assíncronos no WCF 4.5

Há algum tempo eu mencionei aqui como criar um contrato assíncrono para um serviço WCF. A finalidade deste tipo de contrato é permitir a sua implementação de forma, também, assíncrona, para que possamos tirar um hor proveito dos recursos (leia-se threads), do servidor onde o serviço estará sendo hospedado.

Como o formato segue o padrão estabelecido pelo .NET Framework, somos obrigados a construir o contrato nos mesmos moldes do padrão definido por ele, ou seja, um par de métodos Begin/End. Além disso, ainda temos a necessidade de uma eventual implementação da interface IAsyncResult, callbacks, etc., ou seja, com toda a complexidade que estamos acostumados quando precisamos lidar com programação assíncrona no .NET.

As linguagens (C# e VB.NET) redefiniram a forma como escrevemos código assíncrono, e para tirar proveito disso, o WCF sofreu algumas mudanças internas, e agora a criação e implementação de contratos assíncronos são bem mais fáceis de serem implementados do que anteriormente.

Apesar das mudanças serem internas, para que seja possível esse tipo de utilização, algumas regras são exigidas ao (re)escrever o contrato. É necessário que a operação retorne um objeto do tipo Task (quando a mesma não retorna nenhum resultado) ou Task<T> (quando a mesma deve retornar um resultado (T refere-se ao tipo de resultado)). Abaixo temos a interface que define o contrato do serviço:

[ServiceContract]
public interface IContrato
{
    [OperationContract]
    Task<string> Ping(string value);
}

Ao implementar esse contrato na classe que representará o serviço, entre em cena as keywords do C# que faz toda a mágica para executar de forma assíncrona: async e await, quais já falamos aqui, e que em conjunto com os ajustes internos que a Microsoft fez no WCF (a crição de um operation invoker chamado TaskMethodInvoker), faz com que a operação seja executada sem qualquer grande impacto ao desenvolvedor. Abaixo temos um exemplo de implementação do contrato acima criado:

public class Servico : IContrato
{
    public async Task<string> Ping(string value)
    {
        return await Task.Factory.StartNew(() => value + " ping");
    }
}

Em termos que hosting, nada é necessário. Por fim, como já mencionado, em tempo de execução, teremos uma melhor reusabilidade das threads, o que nos permite executar algum recurso custoso, como por exemplo, chamado à bancos de dados, outros serviços, sem que a thread fique bloqueada esperando pelo resultado que não depende das capacidades do processamento local.

Já o consumo por parte de uma aplicação também .NET, temos uma ligeira mudança. Trata-se de uma nova opção chamada "Generate task-based operations". Ao efetuar a referência para um serviço e se esta opção estiver selecionada, fará com que a versão assíncrona das operações do proxy, sejam criadas utilizando o modelo baseado em tasks. Enquanto a outra opção, fará com que ele crie no modelo tradicional, ou seja, baseado em eventos XXXCompleted para cada uma das operações expostas pelo serviço. A imagem abaixo ilustra esta nova opção:

Reportando o Progresso de Tarefas Assíncronas

Uma das necessidades que circundam o processamento assíncrono de alguma tarefa é como reportar o progresso da mesma, para dar um feedback, na maioria das vezes para um usuário, do estágio em que ela se encontra. Na maioria das vezes, isso está associado com interfaces gráficas (GUI), que por sua vez, podem trabalhar utilizando o modelo síncrono ou assíncrono.

Optamos pelo modelo assíncrono justamente permitir que o usuário possa realizar outras tarefas, enquanto aquela que é custosa seja processada por uma segunda thread. É neste ponto que temos a necessidade de saber como anda o progresso do processamento, repassando ao usuário onde se encontra a execução da tarefa.

A Microsoft trabalha em um projeto para tornar a programação assíncrona tão simples quanto a programação síncrona. Este projeto introduz duas keywords, chamadas async e await, que fazem todo o trabalho para "recriar" o código que escrevemos, montando-o da forma necessária para executá-lo de forma assíncrona, sem a necessidade de precisarmos saber como isso é realizado.

Para reportar o progresso temos dois tipos que foram introduzidos: a interface IProgress<T> e a classe EventProgress<T>. A interface genérica IProgress<T> define a estrutura de reporte de progresso, e que não fornece nada mais do que um método chamado Report, que recebe um parâmetro do tipo T, que pode ser qualquer tipo, desde um simples inteiro até uma classe que reporte informações mais detalhes do progresso da tarefa. Já a classe EventProgress<T> é uma implementação desta interface que acabamos de ver, fornecendo a opção de sermos notificados através de um evento. A imagem abaixo mostra a estrutura desses tipos:

A instância da classe EventProgress<T> pode ser criada de duas formas: através da instância diretamente ou através do método estático From. Mas a segunda opção, na versão atual (ainda em CTP), está com um problema, disparando uma exceção de forma indevida. Para exemplificar, temos um formulário Windows Forms, que ao clicar em um botão do mesmo, executaremos uma tarefa custosa, e que deverá reportar ao usuário o progresso da mesmo, utilizando esses tipos que vimos agora, em conjunto com as keywords async/await.

O primeiro passo é instanciar a classe EventProgress<T>, onde o tipo genérico T, será definido como um número inteiro, indicando a porcentagem do progresso, que utilizaremos para incrementar um controle ProgressBar. Depois da classe instanciada, vamos nos vincular ao evento ProgressChanged, que será disparado todo o momento em que o processo custoso desejar reportar o andamento do progresso do mesmo, e como já era de se esperar, um número inteiro será passado como parâmetro através da instância da classe EventArgs<T>, que possui um parâmetro genérico do mesmo tipo em que foi informado na construção da classe EventProgress<T>, acessível através da propriedade Value. O código abaixo ilustra essa parte inicial:

private EventProgress<int> report = new EventProgress<int>();

public Form1()
{
    report.ProgressChanged += (sender, args) => this.progressBar1.Value = args.Value;
}

Com a classe que servirá como forma de reportar o progresso, vamos agora criar o código que simula uma operação custosa, e invocá-lo a partir do evento Click de um botão qualquer. Note que o método ExecutarTarefaCustosa já está decorado com a keyword async. Além disso, temos a keyword await definida no interior do mesmo método, que determina que aquilo que vem na frente dela, é considerado como o callback.

private void button1_Click(object sender, EventArgs e)
{
    ExecutarTarefaCustosa();
}

private async void ExecutarTarefaCustosa()
{
    for (int i = 1; i <= 10; i++)
    {
        await TaskEx.Delay(TimeSpan.FromSeconds(2)); //Simula Processamento Custoso

        ((IProgress<int>)report).Report(i * 10);
    }
}

A cada etapa concluída (iteração), reportamos o progresso através da instância da classe EventProgress<T>. O único detalhe interessante, é que a interface IProgress<T> foi implementada de forma explícita, o que nos obriga a fazermos o casting para conseguir acessar o método Report, informando o parâmetro que representa o status do progresso.

Só que quando trabalhamos com tecnologias como Windows Forms ou WPF, há uma certa dificuldade em fazer a "conversação" entre as threads envolvidas. Por padrão, essas tecnologias utilizam apenas uma única thread, que é criada durante o início do processo. Com isso, todos os controles são criados e gerenciados por essa thread específica. Quando delegamos o processamento assíncrono para uma segunda thread, ela não poderá acessar os controles, pois os mesmos tem afinidade com a thread de criação.

Sabendo desta limitação, a classe EventProgress<T> faz uso de um SynchronizationContext internamente. Essa classe tem como finalidade fornecer uma forma de enfileirar trabalhos a serem realizados em um contexto (thread) específico. Ao criar a instância da classe SynchronizationContext, ela estabelece um vínculo com a thread onde ela está sendo criada, e ao acessá-la dentro de uma segunda thread, ele encaminhará o trabalho para ser realizado na thread ao qual está vinculada.

Finalmente, ao instanciar a classe EventProgress<T>, ela captura o SynchronizationContext corrente. Com isso, quando invocamos o método Report, ele irá disparar o método que vinculamos ao evento ProgressChanged, através do método Post da classe SynchronizationContext, que despacha (também de forma assíncrona) a execução para a thread que criou os controles, que seguramente poderá acessá-los.

O Framework possibilita, a Linguagem facilita

Desde a primeira versão do .NET Framework, várias funcionalidades foram construídas e as linguagens (aqui C# e VB.nET) facilitam o acesso as mesmas, tornando a vida do desenvolvedor muito mais simples. Para mostrar um exemplo simples, podemos perceber que grande parte de nós não utiliza o tipo Int32 para declarar um inteiro. Optamos pela keyword int. Grande parte de nós não utiliza o tipo Boolean para declarar um valor boleano. Optamos pela keyword bool.

Já no .NET Framework 2.0, a Microsoft introduziu no C# os iteradores, que evita a escrita explícita de classes que implementam as interfaces IEnumerable<T> e IEnumerator<T>. Tudo o que ela fez foi criar uma keyword chamada yield, que elimina toda essa necessidade, abstraindo a "complexidade" que esta implementação possui.

Para ter um exemplo mais recente, podemos avaliar as expressões lambdas. Elas nada mais são do que uma forma muito mais simples de se escrever (não de entender) delegates. Salvo a sintaxe de cada linguagem, a Microsoft está tornando tarefas complexas de serem realizadas em um modo bem mais simples de se fazer, ou seja, com novas essas keywords e um jeito diferente de se escrever o mesmo código, grande parte do trabalho complexo agora fica sob responsabilidade do compilador, que avalia e gera a saída necessária para tudo isso funcionar.

Entre as tarefas mais complexas que existem dentro do .NET Framework hoje, é a programação assíncrona. Ela não é nada trivial, pois exige um conhecimento razoável de como as coisas funcionam nos bastidores, o uso do par de métodos BeginXXX/EndXXX, das formas de capturar o resultado (callback ou poll), exceções que são disparadas, etc. Devido a essa complexidade toda, a programação assíncrona foi o alvo da Microsoft para tornar essa tarefa bem mais amena.

Apesar de isso já correr há algum tempo nos bastidores, a Microsoft anunciou hoje no PDC, um novo modelo de programação assíncrona, que permite a escrita do código assíncrono de forma tão simples quanto ao código síncrono. A partir de agora, tudo o que precisamos fazer é declarar a função como sendo assíncrona, e isso é feito através de uma nova keyword chamada async. Além disso, a função também poderá retornar um tipo predefinido que é a instância da classe Task<T>, que como o nome já sugere, representa uma tarefa que está sendo executada.

public async Task<decimal> CalcularSalarioAsync(string empregado)
{
    var resultado = await new ServicoDeSalario().CalcularAsync(empregado);

    return resultado;
}

Analisando o código acima, podemos perceber que também surgiu uma nova keyword: await. Apesar de parecer que ela irá bloquear a thread até que seja finalizado, não é isso o que acontece. Na verdade, quando o compilador encontrar essa keyword, o que ele fará é determinar que o que virá dali para baixo, até o final do método, será o callback que deverá ser disparado quando o processo assíncrono for finalizado.

A finalidade de facilitar isso, é fazer com que a programação assíncrona dentro do .NET Framework seja tão simples quanto a programação síncrona e, consequentemente, tornar as aplicações mais interativas quando falamos daquelas que exigem muitas interações de UI, ou tornando mais simples quando precisamos escrever alguma funcionalidade de forma assíncrona do lado do servidor, como é o caso de páginas/controllers assíncronos do ASP.NET e serviços em geral.

Explorarei mais detalhadamente essa funcionalidade em futuros artigos. Por agora, foi apenas um overview do que está por vir, e também mostrar que algumas complexidades são, aos poucos, absorvidas pelas linguagens. Da mesma forma que as facilidades anteriores que foram criadas dentro das linguagens, esta também será incorporada, mas vale ressaltar que continua sendo importante o seu entendimento interno da mesma forma que é importante conhecer delegates, caso contrário, expressões lambdas serão completamente estranhas.

Silverlight e a chamada assíncrona de serviços

Em geral, podemos consumir os serviços WCF de duas formas: síncrona ou assíncrona. Na primeira opção, ao referenciar o serviço em uma aplicação cliente e invocar um das operações que ele disponibiliza, a comunicação será realizada e enquanto ela não retorna, a aplicação ficará bloqueada aguardando o resultado. Já no segundo modelo, ao realizar a chamada de forma assíncrona, a operação será realizada em um thread secundária, permitindo que a aplicação continue trabalhando enquanto o serviço é executado.

Como sabemos, o Silverlight tem cada vez mais espaço como sendo front-end de aplicações. Essa tecnologia recorre à serviços quando precisa buscar algum conteúdo remoto, como por exemplo, preencher os dados em um controle ListBox quando um botão for pressionado. Atualmente, a maioria desses serviços são construídos em WCF, e referenciados na aplicação Silverlight para permitir que a mesma interaja com o servidor para extrair os dados necessários para executar o seu trabalho.

Para aqueles que já utilizam essa técnica, já devem ter percebido que no Silverlight, só podemos consumir esses serviços de forma assíncrona, ou seja, recorrendo à uma segunda thread através do par de métodos BeginXXX/EndXXX ou através de eventos. Mas porque isso acontece ou porque é necessário?

O Silverlight possui apenas uma única thread, que é chamada de UI Thread, e como sabemos, operações de I/O bound, como é o caso da comunicação através da rede, são tarefas custosas e que podem levar um grande tempo para ser executada, e justamente por isso, se o Silverlight bloqueasse a thread de UI enquanto executa essa requisição, o host (que é o navegador), também seria bloqueado. Mesmo que você tente emular uma chamada síncrona, utilizando algum recurso primitivo de sincronização (como o ManualResetEvent), você teria problemas do mesmo jeito, já que quando invocar o método WaitOne desta classe, ele bloqueará a thread de UI para esperar o resultado, que nunca chegará, pois o resultado somente será entregue para o Silverlight quando a thread de UI não estiver executando nenhum código, que não é o caso aqui, e como já era de se esperar, teremos um deadlock.

Desde o proxy de serviços WCF até classes de baixo nível, como é o caso da WebClient, terão o mesmo comportamento, ou seja, deverão ser sempre acionadas através do modelo assíncrono. Tudo isso se deve ao fato de que os navegadores atuais implementam a NPAPI (Netscape Plugin Application Programming Interface). A NPAPI trata-se de uma API multi-plataforma desenvolvida pela Netscape que permite que plugins sejam utilizados dentro dos navegadores. Para que os plugins sejam considerados multi-plataforma, eles precisam seguir rigorosamente essa API, o que determina que métodos remotos sejam executados assincronamente, e como já percebemos, o Silverlight segue o que foi definido por ela.

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. 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, consultas em 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, é comum o usuário receber 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:

public class 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 processo for 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 chamado Sync, 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.