Métodos Sincronizados

Quando criamos uma classe e dentro dela colocamos métodos, estes podem ser acessados por múltiplas threads ao mesmo tempo. Caso você queira prevenir isso, ou seja, permitir que somente uma única thread por vez o acesse, você pode fazer uso de métodos sincronizados. Tudo o que você precisa fazer para isso funcionar, é decorar o método com o atributo MethodImplAttribute (namespace System.Runtime.CompilerServices), definindo em seu construtor a opção Synchronized, fornecida pelo enumerador MethodImplOptions, assim como é mostrado abaixo:

public class Processo
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    public void Metodo()
    {
        //…
    }
}

Com isso, você terá a garantia de que somente uma thread por vez o executará, enquanto as outras que, eventualmente, cheguem a este mesmo método, ficarão em uma fila, esperando com que a thread atual finalize o seu trabalho.

Apesar de “proteger” as informações, você limitará o acesso à sua classe/tipo. Quando este atributo é aplicado à um método de instância, ele efetuará o bloqueio da instância como um todo (lock(this) { }); já se aplicá-lo em um método estático, ele bloqueará o tipo (lock(typeof(Processo)) { }). A implementação é fácil, mas você será prejudicado com os efeitos colaterais, pois terá maior contenção. Para melhorar o throughput, o ideal é você utilizar alguma técnica de locking que o próprio .NET Framework fornece, para assim refinar o bloqueio da sua classe/tipo.

ConfigurationName

O WCF permite a configuração de um serviço de forma declarativa e imperativa, assim como já falei aqui. Quando optamos pela forma declarativa, todas as configurações do serviço/cliente serão feitas em um arquivo de configuração (App.config/Web.config), e com isso, sempre temos que referenciar o serviço e o contrato com o seu nome completo, ou seja, incluindo o namespace até eles.

Para facilitar isso, temos a propriedade ConfigurationName, exposta através dos atributos ServiceBehaviorAttribute e ServiceContractAttribute, onde podemos definir um “alias” para o serviço e o contrato, respectivamente. Com isso, evitamos especificar o nome completo até os tipos. O código abaixo ilustra a utilização dessa propriedade nos dois atributos:

[ServiceContract(ConfigurationName = “MeuContrato”)]
public interface IContrato
{
    //…
}

[ServiceBehavior(ConfigurationName = “MeuServico”)]
public class Servico : IContrato
{
    //…
}

E, se repararmos no arquivo de configuração agora, teremos:

<services>
  <service name=”MeuServico”>
    <host>
      <baseAddresses>
        <add baseAddress=”http://localhost:9383″/&gt;
      </baseAddresses>
    </host>
    <endpoint address=”srv” binding=”basicHttpBinding” contract=”MeuContrato” />
  </service>
</services>

É importante dizer que isso não interfere no nome do serviço e do contrato que será exposto através do documento WSDL. Essas configurações são apenas características locais dos arquivos de configuração do serviço ou do cliente.

Host de Serviços em Aplicações Windows

Uma das grandes vantagens do WCF é que qualquer aplicação pode servir como hosting para ele. Isso quer dizer que além do tradicional IIS, podemos recorrer à outros tipos, como Windows Services, Console, Windows Forms ou WPF. Cada uma tem as suas vantagens e desvantagens, mas isso já foi abordado neste artigo.

Cada uma dessas opções de hosting, possuem implementações diferentes, e aplicações Windows Forms e WPF tem um sofrimento maior em relação às outras. Isso se deve ao fato de que aplicações Windows não gerenciam apenas o serviço, mas também há uma interface (formulários) para controlar. Ao utilizar um host próprio, temos que recorrer à classe ServiceHost que gerenciará a vida e a execução do serviço. O problema já começa aqui, ou seja, onde criar e manter a instância desta classe?

Antes de prosseguirmos, precisamos analisar o que são os Synchronization Contexts. Synchronization Contexts permite executarmos uma determinada tarefa em uma outra thread, diferente da qual estamos atualmente, representando uma espécie de “canal” entre as duas threads envolvidas, fazendo tudo o que for necessário para isso. Dentro do namespace System.Threading existe uma classe chamada SynchronizationContext, que representa esse “canal”, e o WCF interage com ele através da propriedade boleana UseSynchronizationContext do atributo ServiceBehaviorAttribute, que por padrão é True.

Na configuração padrão, quando criamos o ServiceHost e invocamos o método Open, o WCF irá verificar se há um synchronization context definido e o utilizará. Quando não existir ou a propriedade UseSynchronizationContext é definida explicitamente como False, as operações serão executadas em uma outra thread, que são extraídas do ThreadPool.

Como o Windows Forms cria automaticamente o SynchronizationContext no construtor de um formulário, quando criamos a instância da classe ServiceHost dentro dele, o WCF irá executar todas as operações nesta mesma thread (que também atende aos comandos do usuário (message loop)), pois o contexto já está criado. O exemplo abaixo ilustra isso:

public partial class CadastroDeClientes : Form
{
    private ServiceHost _host;

    public CadastroDeClientes()
    {
        //Neste momento, o context já está criado
        this._host = new ServiceHost(typeof(ServicoDeClientes), new Uri[] { });
    }
}

O problema desta técnica é que você sobrecarregará a thread de UI, já que ela terá que se preocupar com as operações do serviço e com os eventos tradicionais do formulário. Se você optar por abrir o host antes da chamada de um formulário, como por exemplo, dentro do método Main da aplicação, você ainda não terá o contexto estabelecido. Neste caso, qualquer manipulação que você, eventualmente, faça nos controles do formulário dentro das operações do serviço, precisarão de um tratamento especial, pois você não poderá manipular os controles em uma thread diferente da qual eles foram criados.

Finalmente, se você estiver com a propriedade UseSynchronizationContext definida como True (que é o padrão), e estiver consumindo o serviço no mesmo formulário que possui o ServiceHost criado, você terá problemas. Isso se deve ao fato de que a chamada para o serviço bloqueia a thread de UI, enquanto o WCF posta a mensagem para essa mesma thread para invocar o serviço (que está ocupada). Sendo assim, o deadlock será garantido.

Propagando exceções para o Silverlight

O WCF é uma das formas que temos para estabelecer a comunicação entre uma aplicação Silverlight e a aplicação (ASP.NET) que a hospeda. Com o WCF podemos permitir que o Silverlight se conecte e envie e/ou receba informações, pois como sabemos, ele roda dentro do navegador do usuário. Apesar de você não ter acesso a maior parte dos recursos do WCF, o básico você consegue fazer.

Antes da versão 3 do Silverlight, há uma grande dificuldade em propagar as exceções que esse serviço, eventualmente, disparasse. Qualquer exceção que ocorra dentro do WCF, a resposta é enviada ao cliente definindo o código de resposta do HTTP (Http Status Code) como 500, o que indica um erro interno do servidor e impede o navegador de ler qualquer resposta. Esse problema pode ser facilmente contornado, criando um message inspector, verificando antes de enviar a resposta para o cliente, se a mensagem é ou não uma fault. Caso seja, você altera o status do HTTP para 200 (OK) ao invés de 500. O código abaixo ilustra como criar esse behavior customizado:

public class SLFaultMessageInspector : IDispatchMessageInspector
{
    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        if (reply.IsFault)
            reply.Properties[HttpResponseMessageProperty.Name] =
                new HttpResponseMessageProperty() { StatusCode = HttpStatusCode.OK };
    }

    //….
}

Isso ainda poderia ser feito na versão 2 do Silverlight, mas o cliente não conseguia interpretar a fault, ou melhor, traduzí-la para uma exceção, e permitir que o desenvolvedor trate o erro da forma tradicional. Já a versão 3, incorpora a capacidade ao Silverlight de processar as faults, podendo especificar os contratos de faults ou optar por não especificá-los, para assim propagar ao cliente os objetos que representam e trazem maiores informações sobre o problema que ocorreu do outro lado.

ASP.NET Session em serviços WCF

Como foi visto aqui, podemos utilizar as variáveis de sessão do ASP.NET para manter o estado de um serviço WCF. Para isso, basta habilitar o modo de compatibilidade com ASP.NET e você já terá acesso ao HttpContext (HttpContext.Current) em seu serviço.

Você precisa ter um certo cuidado aqui com dois detalhes: o primeiro deles é com relação a dependência que o seu serviço terá com o protocolo HTTP, pois dentro da classe que representa o serviço, você mencionará classes que fazem parte do ASP.NET, assim como a HttpContext, e com isso, você não conseguirá acessá-lo através de TCP. Já o segundo detalhe, é com relação ao consumo deste serviço. Ao contrário do gerenciamento de instâncias do WCF, a sessão do ASP.NET é utilizada de forma diferente, mas com a mesma finalidade, ou seja, de manter o estado das informações durante as requisições.

Quando você faz uso do modelo PerSession, ao fechar o proxy o WCF descartará a instância da classe do lado do serviço. Como no caso do ASP.NET é ele quem controla as variáveis de sessão, você precisará explicitamente removê-las. Para que você consiga descartar tudo o que tem na memória, precisará chamar o método Abandon da classe HttpSessionState. Para automatizar essa tarefa e não poluir a Interface do contrato com métodos que “limpam” a sessão, você pode implementar na classe do serviço a Interface IDisposable, que será invocada quando o proxy for encerrado.

Como sabemos, a configuração padrão da sessão, é manter um cookie do lado do cliente com o “Id”, para que ela consiga identificar o usuário. Para que isso funcione corretamente, precisamos habilitar o gerenciamento de cookies pelo binding, assim como é mostrado neste artigo. Com isso, o proxy do WCF encaminhará, automaticamente, os cookies nas futuras requisições. Mesmo que você utilize instâncias diferentes do proxy, devido ao recurso de caching, elas compartilharão as mesmas variáveis de sessão do lado do serviço.

Você pode consumir um serviço WCF na mesma aplicação em que ele está hospedado (comum em aplicações Silverlight). Mesmo que você utilize o mesmo diretório virtual/worker process, você não conseguirá dentro do serviço acessar as variáveis de sessão criadas no ASP.NET e vice-versa. Lembre-se de que o serviço nem sempre será consumido por essa mesma aplicação, e justamente por isso, é proibido. Se você precisar passar informações que estão armazenadas em variáveis de sessão do lado do cliente, tudo o que você precisa fazer, é enviá-las através do contrato.

Overloading de métodos no WCF

Um outro detalhe que funciona perfeitamente bem no C# ou qualquer linguagem orientado a objetos, é o overloading de métodos, onde você tem várias métodos com o mesmo nome, variando o tipo ou a quantidade de parâmetros. Podemos aplicar essa técnica também em Interfaces, e quando ela for implementada nas classes, teremos tais métodos a nossa disposição.

A questão é que, assim como a implementação explícita, quando aplicado em uma Interface que servirá como contrato de um serviço WCF, isso não funcionará. Mas isso não é uma limitação da plataforma, mas sim do documento WSDL, que não suporta algumas características de programação orientada à objetos, e a sobrecarga de métodos é uma delas. Com essa “limitação”, ao rodar um serviço (hosting) com algum método que possua algum overload, você receberá uma exceção do tipo InvalidOperationException, indicando que isso não é suportado.

Para resolver, basta recorrermos a propriedade Name do atributo OperationContractAttribute. Quando essa propriedade é omitida, o WCF assume o nome do método para publicar no documento WSDL. Eventualmente, você poderá customizar, escolhendo o nome que será exposto, já que você não está obrigado a manter o mesmo nome, que muitas vezes reflete algo intuitivo para a sua aplicação, mas incoerente com a operação que será publicada pelo serviço. O exemplo abaixo, ilustra como proceder para utilizar este atributo:

[ServiceContract]
public interface IContrato
{
   [OperationContract(Name = “RecuperarClientesPorEstado”)]
   string[] RecuperarClientes(string estado);

   [OperationContract(Name = “RecuperarClientesPorCidade”)]
   string[] RecuperarClientes(string estado, string cidade);
}

Ao referenciar esse serviço em um aplicação cliente, o mesmo enxergará dois métodos, com os respectivos nomes: “RecuperarClientesPorEstado” e “RecuperarClientesPorCidade”, baseando-se no alias que demos ao criar o contrato. Apesar da ferramenta criar dois métodos, você poderá customizar o proxy gerado, recriando os overloads e tornando a programação do lado do cliente mais intuitiva. Ao gerar o proxy – automaticamente – do lado do cliente, o contrato é criado da seguinte maneira:

[System.CodeDom.Compiler.GeneratedCodeAttribute(“System.ServiceModel”, “3.0.0.0”)]
[ServiceContractAttribute(ConfigurationName=”Servico.IClientes”)]
public interface IClientes {
    [
        OperationContract(
            Action=”http://tempuri.org/IClientes/RecuperarClientesPorEstado&#8221;, 
            ReplyAction=”http://tempuri.org/IClientes/RecuperarClientesPorEstadoResponse&#8221;)
    ]
    string[] RecuperarClientesPorEstado(string estado);

    [
        OperationContract(
            Action=”http://tempuri.org/IClientes/RecuperarClientesPorCidade&#8221;, 
            ReplyAction=”http://tempuri.org/IClientes/RecuperarClientesPorCidadeResponse&#8221;)
    ]
    string[] RecuperarClientesPorCidade(string estado, string cidade);
}

Para que você consiga refazer o overloading do lado do cliente, você precisa seguir a mesma técnica aplicada do lado do serviço, ou seja, adicionar a propriedade Name no atributo OperationContractAttribute, renomear as operações para “RecuperarClientes” e, finalmente, reimplementar o contrato na classe que representa o proxy. Desta forma, o cliente e o serviço trabalharão da forma tradicional (OO), enquanto em runtime, tudo será tratado com os “aliases”.

Método Zip

Um novo método (de extensão) foi adicionado à classe Enumerable na versão 4.0 do .NET, chamado de Zip. Dado duas coleções/arrays, esse método tem a finalidade de aplicar uma espécie de “zíper” entre eles, ou seja, agrupando os elementos correntes de cada coleção, onde o resultado é a combinação entre esses dois elementos. Para exemplificar, considere os dois arrays de inteiros:

int[] pares = new int[] { 0, 2, 4, 6, 8 };
int[] impares = new int[] { 1, 3, 5, 7, 9 };

foreach
(var item in impares.Zip(pares, (i, p) => string.Format(“{0}, {1}, “, p, i)))
    Console.Write(item);

O resultado ficará: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. O processamento se encerra quando não for encontrado um elemento “correspondente” na outra coleção.

Configuração do WCF no cliente

Quando fazemos a referência a um serviço WCF em uma aplicação cliente, o arquivo de configuração sofre uma grande mudança, pois todas as configurações de acesso ao serviço (endpoints, bindings, behaviors, etc.), são colocadas dentro deste arquivo. Muitas vezes, o arquivo de configuração de uma aplicação já possui várias outras entradas, e colocar todas essas outras configurações, torna o arquivo muito extenso.

A partir do WCF 4.0, a Microsoft incluiu uma nova classe chamada ConfigurationChannelFactory<TChannel> (System.ServiceModel.Configuration), que herda diretamente da classe ChannelFactory<TChannel>. Com essa classe, podemos isolar as configurações do WCF em um arquivo de configuração a parte, dando uma maior flexibilidade, já que podemos criar um proxy (DLL) e distribuir o respectivo arquivo de configuração, e aquele que consume, não precisa fazer nada além de colocar o arquivo no diretório da aplicação.

Para trabalhar com essa classe, temos que recorrer a criação manual do proxy, assim como foi falado aqui. Primeiramente precisamos utilizar a classe ExeConfigurationFileMap, que recebe o nome de um arquivo de configuração, e que neste caso, conterá as configurações necessárias para o serviço. Em seguida, abrimos esse arquivo mapeado e o passamos para a classe ConfigurationChannelFactory<T>. Depois disso, tudo fica conforme já sabemos, invocando o método CreateChannel e, finalmente, o consumo das operações. O código abaixo ilustra esse exemplo:

ExeConfigurationFileMap map = new ExeConfigurationFileMap() { ExeConfigFileName = “wcf.config” };
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationLevel.None);
ConfigurationChannelFactory<IContrato> factory =
       new ConfigurationChannelFactory<IContrato>(
              “NetTcpBinding_IContrato”,
              config,
              new EndpointAddress(“net.tcp://localhost:9292”));
IContrato proxy = factory.CreateChannel();
Console.WriteLine(proxy.Ping(“teste”));

Internals – Proxy de serviços WCF

Para aqueles que desejam consumir um serviço (WCF ou não) através da infraestrutura do WCF que existe do lado do cliente, existem duas opções para referenciá-lo na aplicação cliente, e cada uma dessas opções fornecem suas vantagens e desvantagens. A finalidade deste artigo é tentar descrever como as coisas funcionam nos bastidores, para conseguirmos tirar o melhor proveito disso.

A mais comum entre as formas de acesso um serviço, é a geração do proxy de forma automática, que através da IDE do Visual Studio ou do utilitário svcutil.exe, basta que eu informe o endereço do documento WSDL, e uma classe será criada com toda a estrutura necessária para gerenciar e invocar operações do serviço remoto. Nesta opção, todos os tipos que, eventualmente, o serviço exponha, serão automaticamente reconstruídos do lado do cliente, para que o mesmo seja capaz de enviar e/ou receber tais tipos.

Entre os tipos que são reconstruídos do lado do cliente, uma Interface é criada representando o contrato do serviço, contendo todas as operações que ele fornece. Além disso, uma classe também é criada, herdando da classe abstrata ClientBase<TChannel>. Essa classe é a que conhecemos como “proxy”. Ao herdar de ClientBase<TChannel>, o parâmetro genérico TChannel é substituído pela Interface que representa o contrato do serviço, que como vimos, foi reconstruída do lado do cliente. Além disso, a Interface do contrato do serviço também é implementada nesta classe, ou seja, ao instanciar o proxy, veremos os métodos que foram disponibilizados no serviço. Durante o tempo de execução, ao invocar esses métodos locais, o WCF encaminhará a requisição para o serviço.

Essa classe nada mais é do que um wrapper, que visa facilitar a vida de quem consome o serviço fornecendo, além dos métodos que refletem as suas operações, métodos que gerenciam a vida do canal de comunicação, tais como Open, Close, Abort, etc. Ao criar a instância desta classe ou descartá-la, uma série de tarefas custosas e complexas são realizadas, tais como: carregar as configurações do arquivo de configuração, criar toda a árvore de tipos que descreve o serviço, construção da channel stack (a complexidade depende das funcionalidades habilitadas) e o descarte dos recursos que a mesma utiliza para efetuar a comunicação.

Internamente essa classe mantém/referencia um objeto do tipo ChannelFactory<TChannel>, que é uma factory responsável por criar e configurar, efetivamente, o canal de comunicação entre o cliente e o serviço. Antes do .NET Framework 3.5 + SP1, essa classe era instanciada uma única vez, e depois é mantida viva durante a instância do proxy. Na versão mais recente, não existe mais essa afinidade, ou seja, a instância da ChannelFactory<TChannel> é mantida em nível do AppDomain, e sendo assim, mesmo que você crie e descarte o proxy a todo o momento, grande parte do processo complexo e custoso, será mantido e reutilizado independentemente de quantas instâncias do proxy você venha a criar, gerando uma espécie de cache.

Esse cache melhora consideravelmente o custo de inicialização, que na maioria das vezes, é sempre igual. Esse cache trabalha no formato MRU (most recently used), e está “limitado” à 32 objetos, e quando essa capacidade for atingida, os últimos vão sendo removidos. A cada instância do proxy, antes do WCF efetivamente criar a instância da classe ChannelFactory<TChannel> correspondente, ele interroga o cache a procura deste objeto (que atenda a essas mesmas características), e caso o encontre, evitará a criação de um novo.

Um cuidado especial que se deve ter é com relação as versões que temos no cache. O construtor do proxy possui vários overloads, e os objetos que estão colocados no cache são versionados de acordo com esses parâmetros. Aqueles overloads que não aceitam um Binding como parâmetro, seguramente, objetos que estão no cache serão reutilizados, já que os demais parâmetros são imutáveis. Quando um Binding é passado como parâmetro para um dos construtores, o caching será desabilitado, já que não há como garantir que as informações definidas na instância do binding serão sempre as mesmas. A mesma regra se aplica quando você acessa as propriedades ChannelFactory, Endpoint e ClientCredentials do proxy antes do canal de comunicação ser criado e aberto.

Voltando as formas que temos de invocar um serviço, a segunda possibilidade é utilizar o ChannelFactory<TChannel> diretamente, sem criar o proxy por algumas das ferramentas que vimos acima. Essa técnica nos permite ter um maior controle do caching, já que essa funcionalidade ficará sob nossa responsabilidade. Você pode manter a factory em algum ponto do teu código, e quando precisar de um novo canal de comunicação com as mesmas configurações, basta invocar o método CreateChannel, que ele te retorna uma instância configurada. O ponto negativo desta técnica, é que o cliente já tem que, de alguma forma, conhecer os tipos e contratos que são expostos pelo serviço, cenário comum quando utilizamos assemblies compartilhados. O código abaixo ilustra como podemos proceder para fazer isso:

ChannelFactory<IContrato> factory =
    new ChannelFactory<IContrato>(new NetTcpBinding(), “net.tcp://localhost:3922/srv”);

IContrato proxy = factory.CreateChannel();
Console.WriteLine(proxy.Ping(“Israel”));

Além de você já precisar conhecer os tipos, para invocar qualquer método que gerencie a vida do proxy (Open, Close, Abort, etc.), você precisará converter explicitamente para ICommunicationObject. E, para finalizar, quando não precisar mais da factory, tudo o que precisa fazer é também invocar o seu método Close, que descarta todos os recursos custosos que ela armazena. O exemplo abaixo ilustra como podemos proceder para encerrar o proxy e, eventualmente, a factory:

((ICommunicationObject)proxy).Close();

//Eventualmente
factory.Close();

Conclusão: Ambas técnicas tem seus pontos positivos e negativos. Muito do que vimos neste artigo, acaba sendo gerenciado de forma automática pelo WCF, mas conhecendo alguns detalhes internos como estes, nos permite identificar problemas de performance que, eventualmente, podemos estar sofrendo.

Habilitando o Performance Counter no WCF

Como eu comentei aqui, o WCF fornece vários contadores de performance que você pode utilizar para monitorar os serviços. Há vários tipos de contadores, que mensuram pontos cruciais de toda a infraestrutura do WCF. A imagem abaixo ilustra o monitoramento das instâncias (das classes que representam o serviço) que são criadas, e a quantidade de chamadas por segundo.

Ao adicionar um contador, você pode escolher monitorar esses índices para todos os serviços que rodam naquela máquina ou, se desejar, poderá monitorar esse contador para um serviço específico. Para isso, ao adicionar um contador, selecione a instância correspondente, que é representada pelo endereço onde o serviço está exposto. Se por algum motivo, quiser ler essas informações a partir de uma aplicação customizada, então você deve recorrer ao uso da classe PerformanceCounter e PerformanceCounterCategory.