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:

ASP.NET Web API: Implementação Assíncrona

A Microsoft trabalha para que na próxima versão do C# e do VB.NET, eles já saiam com suporte para programação assíncrona. Como eu já havia mencionado em um post anterior, a ideia é facilitar a programação assíncrona, que não é nada trivial. A ideia é tornar a escrita de um código assíncrono, muito próximo a escrita de um código síncrono, e nos bastidores, o compilador faz grande parte do trabalho. Grande parte das funcionalidades do .NET Framework que já possuem suporte nativo ao consumo em formato assíncrono, foram readaptados para que assim, os desenvolvedores possam fazer uso dos recursos oferecidos pela linguagem para consumi-los.

Desta mesma forma, algumas tecnologias que dão suporte à construção de funcionalidades assíncronas, também sofreram adequações para este novo modelo. Para citar um caso, o WCF permite agora a construção de um serviço assíncrono de forma muito mais tranquila que antes, onde tínhamos que implementar um contrato assíncrono.

Já o ASP.NET merece algumas considerações: o ASP.NET Web Forms traz o conceito de páginas assíncronas, que também farão uso dos recursos das linguagens para implementa-las. O ASP.NET MVC possui uma classe chamada AsyncController, de qual herdamos e com pequenos ajustes, conseguimos ter ações sendo disparadas de forma assíncrona. Na próxima versão, o ASP.NET MVC também flexibilizará a criação das ações assíncronas, deixando, novamente, o trabalho árduo para o compilador.

Agora, ao utilizar o ASP.NET Web API, também podemos fazer com que as ações expostas pela API sejam processadas assincronamente. Como é a primeira versão desta tecnologia, a forma de escrevermos as ações assincronamente, é somente através dos recursos das linguagens. Só que antes de exibir como procedemos para criar o código assíncrono, vamos primeiramente criar a versão síncrona desta ação:

public class NoticiasRegionaisController : ApiController
{
    private const string ServicoDeNoticias =
        “http://localhost:1256/api/noticias/recuperar&#8221;;

    [HttpGet]
    public IEnumerable<NoticiaDaRegiao> Recuperar()
    {
        return
            JsonArray.Parse
            (
                new WebClient().DownloadString(ServicoDeNoticias)
            ).ReadAsType<IEnumerable<NoticiaDaRegiao>>();
    }
}

Note que há um serviço chamado NoticiasRegionais, qual recorre à um outro serviço que é mais abrangente, para extrair as notícias de uma determinada região ou cidade. No interior do método Recuperar, propositalmente fazemos a chamada para o serviço e esperamos pelo resultado, que ao voltar, efetuamos o parser e, finalmente, convertemos para o formato esperado e retornamos ao cliente.

Ao rodar esse código, a requisição será bloqueada pelo runtime até que o resultado seja devolvido pelo serviço. Isso prejedica, e muito, a escalabilidade do serviço. O fato da thread ficar bloqueada enquanto espera pelo resultado do serviço, ela poderia estar atendendo à outras requisições, que talvez não exijam recursos de terceiros (I/O bound). O fato de disponibilizar a thread para que ela possa atender à outras requisições, farão com que elas não esperem por um – longo – tempo indeterminado, pois como dependemos do resultado de um terceiro, poderíamos arranjar muito trabalho para esta thread, até que ela precise retomar o trabalho da requisição anterior.

O gráfico abaixo apresenta algumas medições que foram feitas no serviço acima, que foi implementado de forma síncrona. Entre os contadores podemos visualizar a média do tempo gasto para que a requisição retorne, e além disso, a quantidade de requisições que o serviço de notícias regionais é capaz de atender por segundo.

Como mencionado acima, podemos implementar o controller da API de forma assíncrona, o que exigirá algumas mudanças, mas nada que faça com que seja necessário escrever e/ou gerenciar uma porção de código para garantir o assincronismo. Com isso, o primeiro detalhe a notar na escrita da ação assíncrona, é a exigência da keyword async, que faz parte do C#. Além disso, uma das grandes diferenças aqui, é com relação ao tipo de retorno da ação. No exemplo anterior, retornávamos um IEnumerable<T>, onde o tipo T é representado pela classe NoticiaDaRegiao. Aqui, ao invés disso, vamos retornar um Task<TResult>, onde TResult será definido com o mesmo tipo de retorno da ação síncrona, ou seja, IEnumerable<NoticiaDaRegiao>.

Internamente a implementação também mudará. Passamos a recorrer a classe HttpClient, que fará parte do .NET Framework, utilizado para consumir serviços REST, fornecendo vários facilitadores. Além disso, o fato de colocarmos a keyword async na assinatura do método, o compilador já nos obriga a definir o local que vamos aguardar pelo resultado, e para isso, utilizamos uma segunda keyword, chamada de await. No exemplo abaixo aguardamos a resposta voltar, e além disso, também invocamos a leitura do corpo da mensagem de forma assíncrona. Com isso, a ação que tínhamos foi rescrita e ficou da seguinte forma:

public class NoticiasRegionaisController : ApiController
{
    private const string ServicoDeNoticias =
        “http://localhost:1256/api/noticias/recuperar&#8221;;

    [HttpGet]
    public async Task<IEnumerable<NoticiaDaRegiao>> Recuperar()
    {
        using (var client = new HttpClient())
            return await
                (await client.GetAsync(ServicoDeNoticias))
                .Content
                .ReadAsAsync<IEnumerable<NoticiaDaRegiao>>();
    }
}

Com isso, ao executarmos novamente os testes, agora contra o serviço implementado de forma assíncrona, veremos um ganho considerável, onde o tempo de resposta diminui e a quantidade de requisições atendidas por segundo aumenta. O gráfico abaixo já reflete essa mudança:

A implementação assíncrona trouxe um resultado interessante, aumentando a capacidade da aplicação de lidar com muitas requisições, e isso tende a aumentar dependendo das características do ambiente, como máquina, recursos, funcionalidades do serviço, etc.