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:

Publicidade

Compressão em Serviços WCF 4.5

Serviços WCF que são hospedados no IIS sempre puderam usufruir da compactação de mensagens, já que bastar habilitar, pois o próprio servidor fornece este recurso. Serviços que são hospedados em seu próprio servidor (self-hosted), como é o caso de aplicações Windows (Windows Forms, WPF, Console ou Windows Service), precisam de um trabalho extra, que consiste em criar um message encoder customizado para isso. Inclusive uma implementação padrão é fornecida no pacote de demonstrações oferecidos pela própria Microsoft.

A partir do WCF 4.5, nós teremos esse recurso nativamente, ou seja, o codificador binário (BinaryMessageEncoding) passa a fornecer uma propriedade chamada CompressionFormat, que determina o formato da compressão, podendo escolher entre GZip, Deflate ou None. Como essa é uma propriedade fornecida pelo encoder, temos que customizar a criação da binding, para que assim tenhamos acesso à propriedade que define o padrão da compressão. O código abaixo ilustra a criação deste binding, e logo na sequência, temos a mesma configuração, só que através do modelo declarativo (via arquivo de configuração):

var binding = 
    new CustomBinding
    (
        new BinaryMessageEncodingBindingElement()
        {
            CompressionFormat = CompressionFormat.GZip
        },
        new HttpTransportBindingElement()
    );

<customBinding>
  <binding name=”BinaryCompressionBinding”>
    <binaryMessageEncoding compressionFormat =”GZip”/>
    <httpTransport />
  </binding>
</customBinding>

Se interceptarmos a requisição para um serviço sem qualquer configuração de compactação, enviando uma string com 5000 caracteres, temos a requisição abaixo (removendo alguns elementos por questões de espaço):

POST http://127.0.0.1:9291/srv HTTP/1.1
Content-Type: application/soap+msbin1
Host: 127.0.0.1:9291
Content-Length: 5157
Accept-Encoding: gzip, deflate

Agora, se optarmos pelo modelo de compactação GZip, ao analisarmos a requisição para o mesmo serviço, podemos reparar que a quantidade de bytes trafegados é bem menor quando comparado à primeira requisição:

POST http://127.0.0.1:9291/srv HTTP/1.1
Content-Type: application/soap+msbin1+gzip
Host: 127.0.0.1:9291
Content-Length: 185
Accept-Encoding: gzip, deflate

Apesar de um ganho significativo, é importante avaliar corretamente o ambiente em qual o serviço será disponibilizado. Enquanto a compactação é interessante quando o gargalo é a rede, a mesma exige muito mais da CPU para realizar o processo de compactação e descompactação. Sendo assim, o melhor é analisar e realizar alguns testes, e mensurar o que realmente importa durante a execução no ambiente de produção.