Desenvolvimento - C#

WCF - Sincronização

A finalidade deste artigo é mostrar as três opções fornecidas pelo WCF para tratar a concorrência e, além disso, exibir algumas das várias técnicas de sincronização fornecidas pelo .NET Framework e que poderão ser utilizadas em conjunto com o WCF.

por Israel Aéce



function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i Ao expor um serviço para que ele seja consumido, devemos nos atentar à possibilidade deste serviço ser acessado simultaneamente. Isso ocorre quando múltiplas requisições (threads) tentam acessar o mesmo recurso ao mesmo tempo. A possibilidade de acessos simultâneos poderá acontecer dependendo do tipo de gerenciamento de instância escolhido para o serviço. A finalidade deste artigo é mostrar as três opções fornecidas pelo WCF para tratar a concorrência e, além disso, exibir algumas das várias técnicas de sincronização fornecidas pelo .NET Framework e que poderão ser utilizadas em conjunto com o WCF.

É importante dizer que os modos de concorrência aqui abordados não garantem a segurança ao acesso à algum recurso compartilhado. Isso fica sob responsabilidade do desenvolvedor que está desenvolvendo o serviço e, para isso, como falado acima, devemos recorrer às classes já existentes dentro do .NET Framework que permitem a sincronização e proteção à tais recursos em um ambiente multi-threading. Há alguns casos onde a combinação entre o modo de gerenciamento de instância com o modo de concorrência já garantem isso e veremos mais abaixo como aplicá-los.

Quando uma requisição chega para um serviço, uma thread é retirada do ThreadPool para servir à requisição, mais precisamente, uma thread de I/O. Quando múltiplas requisições chegam ao serviço e ele, por sua vez, suporta que múltiplas requisições o acessem, então precisamos saber como tratá-las. Um ponto importante aqui é que a concorrência poderá ou não existir dependendo do modo de gerenciamento de instância do serviço. Para recapitular, temos três possibilidades: PerSession, PerCall e Single.

No modo PerSession uma instância será criada e mantida enquanto durar o proxy. Caso o cliente utilize o mesmo proxy para realizar várias chamadas ao mesmo tempo, então poderá haver concorrência entre as requisições de um mesmo cliente; já no modelo PerCall tradicional a concorrência não existirá, pois para cada chamada de uma determinada operação uma nova instância do serviço será criada para atendê-la. Infelizmente o modelo PerCall não está totalmente seguro em relação aos problemas de concorrência pois, o mesmo poderá fazer o uso de um recurso compartilhado entre várias chamadas (como caching, variáveis estáticas, etc.). Finalmente, o modelo Single é o modo de gerenciamento de instância mais propício aos problemas, já que toda e qualquer requisição será direcionada para a mesma instância e, caso os recursos compartilhados não estejam devidamente assegurados, podemos ter problemas de deadlocks ou contenção.

Para tratar a concorrência o WCF fornece uma propriedade chamada ConcurrencyMode, que está definida no atributo ServiceBehaviorAttribute, ou seja, é uma característica do serviço e não do contrato. Essa propriedade aceita uma das três opções definidas no enumerador ConcurrencyMode, as quais estão listadas abaixo:

  • Single: O serviço poderá aceitar apenas uma chamada por vez, não aceitando re-entradas (utilizadas em callbacks). Para as mensagens que chegarem enquanto uma requisição está sendo processada, ele aguardará até que o processo corrente seja finalizado. Quando a opção não é especificada, este valor é definido como padrão.

  • Reentrant: Neste modelo, o serviço poderá aceitar apenas uma chamada por vez, assim como o Single, mas permitirá que re-entradas sejam realizadas (utilizadas em callbacks), evitando assim o deadlock.

  • Multiple: Este modelo suporta multi-threading. Isso possibilitará que múltiplas threads entrem ao mesmo tempo no serviço e, caso existam recursos compartilhados, precisará utilizar técnicas para garantir o acesso seguro à eles, evitando possíveis deadlocks.

O exemplo abaixo ilustra como devemos proceder para configurar uma das opções de gerenciamento de concorrência na classe que representa o serviço:

using System;
using System.ServiceModel;

[ServiceBehavior(
    InstanceContextMode = InstanceContextMode.PerSession
    , ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class ServicoDeClientes : ICliente
{
    //implementação
}
Imports System
Imports System.ServiceModel

<ServiceBehavior(
    InstanceContextMode := InstanceContextMode.PerSession
    , ConcurrencyMode := ConcurrencyMode.Reentrant)>
Public Class ServicoDeClientes
    Implements ICliente

    "implementação
End Class
C# VB.NET

Observação importante: Antes de prosseguir é importante que você tenha conhecimento em algumas funcionalidades disponibilizadas pelo .NET Framework para sincronização e proteção de recursos em um ambiente multi-threading. Algumas destas funcionalidades serão abordadas neste artigo, mas o seu funcionamento não será comentado profundamente, já que isso está fora do escopo do artigo. Para um melhor entendimento destas funcionalidades, você poderá consultar o Capítulo 14 deste artigo.

Single

Quando a concorrência de um serviço é definida como Single, o mesmo será bloqueado enquanto uma requisição está sendo atendida, impossibilitando que outras requisições executem ao mesmo tempo. Se elas existirem, elas serão enfileiradas aguardando que o processo finalize para que elas sejam processadas, na respectiva ordem que chegaram. Esse modelo é o mais seguro, já que você não precisará lidar com a proteção de recursos compartilhados, pois o próprio WCF garante que apenas uma única thread será processada por vez.

Uma vez que a requisição corrente é encerrada, o WCF desbloqueia o serviço, permitindo que a próxima mensagem da fila (caso exista) seja processada. Quando esta mensagem começar a ser processada, ela bloqueia novamente o serviço, mantendo este bloqueio até que o método seja finalizado. Esse modo de concorrência diminui os problemas mas, da mesma forma, diminui o throughput.

Baseando-se no modo Single de concorrência, quando o modelo de gerenciamento de instância da classe estiver definido como PerSession e alguma requisição seja realizada para o serviço através do mesmo proxy, essa segunda chamada deverá aguardar até que a primeira seja completamente finalizada. No modelo PerCall não há concorrência, já que sempre haverá uma instância exclusiva para cada requisição e não sendo compartilhada com nenhuma outra chamada. Finalmente, se o modo de gerenciamento de instância estiver também definido como Single, ou seja, uma única instância servindo todas as requisições, apenas uma única chamada será processada por vez, bloqueando todas as outras, colocando-as em uma fila e não tendo problemas com concorrência.

Reentrant

Antes de falar do modo Reentrant, é necessário entender uma funcionalidade que é fornecida pelo WCF e que é o grande alvo deste modo de gerenciamento de concorrência: callbacks. Os callbacks possibilitam a comunicação bidirecional entre o cliente e o serviço e também são referidos como comunicação duplex. Neste tipo de comunicação será necessário definir um callback para que o contrato possa, em um determinado momento, disparar um método pré-definido do lado do cliente. A criação deste tipo de mensagem está fora do escopo do artigo e assume que você já tenha o respectivo conhecimento. Caso ainda não possua, você pode consultar este artigo.

Este modo de tratamento de concorrência segue a mesma idéia do modelo anterior (Single), ou seja, apenas é permitido uma requisição ser processada por vez; entretanto, esse modo possui uma customização: podemos deixar momentaneamente o serviço para a realização de alguma tarefa externa (como a chamada para outro serviço) e, via callback, voltarmos seguramente ao mesmo serviço, continuando a execução do mesmo, sem causar deadlock. É importante dizer que durante esta "janela", possíveis requisições que estiverem na fila, aguardando o processamento, poderão entrar no serviço (em sua respectiva ordem). Quando a thread que deixou o serviço para executar uma outra tarefa for finalizada, ela será recolocada na fila para ser processada, ou melhor, dar continuidade no processamento que já existia antes do callback.

Quando o serviço está exposto como PerCall e ele exige uma re-entrada (isso ocorre quando há callbacks), então será necessário definí-lo como Reentrant para não causar deadlock com ela mesma; a re-entrada não sofrerá problemas com bloqueios em relação às outras requisições, pois no modo PerCall a instância somente servirá àquela requisição. Já quando o modo de gerenciamento de instância estiver definido como PerSession e o de concorrência como Reentrant, o callback será disparado e, quando o controle voltar para o serviço e já existir alguma outra requisição sendo executada, o retorno do callback será bloqueado até que a requisição corrente seja finalizada. Essa característica permite aumentar o throughput para clientes que fazem chamadas multi-threading, permitindo que enquanto o callback seja processado, outra chamada para o serviço possa ser executada. Finalmente, com o modelo de gerenciamento de instância definido como Single, o callback será disparado e, quando o controle voltar, ele será enfileirado caso exista alguma requisição em processamento.

Um cuidado especial que se deve ter ao utilizar o modo Reentrant é o gerenciamento de estado dos membros internos utilizados pelo serviço. Como vimos anteriormente, uma vez que o callback for executado, outras chamadas poderão ocorrer em paralelo, mas há um detalhe: quando o serviço for executar o callback, é importante que ele mantenha o serviço em um estado consistente para atender as outras requisições; da mesma forma, quando o callback retornar, é necessário atualizar as informações utilizadas pelo método, pois durante a chamada externa possivelmente outras chamadas foram realizadas, podendo modificar as informações que ele está (estava) utilizando.

A instância do proxy que iniciou o processo deverá ficar ativa até que o callback seja disparado pelo serviço e, caso isso não aconteça, uma exceção do tipo ProtocolException será disparada. Além disso, é importante saber que o callback é sempre executado em uma thread diferente da thread que iniciou a chamada para o método. Isso quer dizer que, quando estamos utilizando esta técnica em aplicações Windows Forms, devemos nos atentar quando precisarmos acessar controles do formulário a partir do método de callback. Isso se deve ao fato de que os controles são criados em uma thread diferente (a mesma que iniciou a chamada para o método através do proxy), o que nos obriga à acessá-los a partir dela. Há algumas facilidades que o Windows Forms fornece em conjunto com o .NET Framework, mas não serão abordadas aqui.

Multiple

Utilizar o modo de concorrência como Multiple em conjunto com o modelo de gerenciamento de instância PerSession ou Single permitirá que uma mesma instância atenda à diversas requisições (threads), aumentando assim o throughput. O problema que existe nestes cenários é a possibilidade do serviço utilizar um recurso compartilhado entre essas chamadas, o que obrigará a proteger manualmente tais recursos, utilizando as técnicas de sincronização existentes. No modelo PerCall a opção de concorrência Multiple é irrelevante, já que neste modo haverá sempre uma instância servindo cada requisição.

Dependendo dos recursos que estão sendo utilizados pelo serviço, devemos protegê-los do acesso concorrente. Se estiver acessando algum recurso que já possua o devido tratamento, então não há com que se preocupar. Por outro lado, se o serviço mantém recursos que são acessados por múltiplas threads, como é o caso de membros internos da classe, então será necessário protegê-los manualmente. Caso você não faça isso, é grande a probabilidade de ocorrer deadlocks, race conditions e o conflito de informações durante o processamento.

Comparativo

Instância/Concorrência Single Reentrant Multiple
PerSession
  • Limita o throughput em chamadas concorrentes
  • Chamadas concorrentes podem somente ser processadas entre diferentes proxies
  • Não há necessidade de trabalhar com mecanismos de bloqueio
  • Aumenta o throughput em chamadas concorrentes
  • Permite acesso multi-thread quando o callback está sendo executado
  • É necessário ter cuidados especiais com o estado dos membros internos do serviço
  • Permite acesso multi-thread através de um mesmo proxy
  • Há necessidade de trabalhar com mecanismos de bloqueio
PerCall
  • Obterá um deadlock
  • Irrelevante, já que há uma instância do serviço exclusiva
  • Irrelevante, mas tendo cuidados especiais quando acessar um recurso externo e compartilhado (variáveis estáticas)
Single
  • Limita o throughput
  • Garantia de que o código não será acessado por várias requisições ao mesmo tempo
  • Não há necessidade de trabalhar com mecanismos de bloqueio
  • Enquanto um callback estiver sendo executado, a próxima requisição (caso exista) será executada
  • Aumenta o throughput quando há chamadas enfileiradas
  • É necessário ter cuidados especiais com o estado dos membros internos do serviço
  • Não há bloqueio à nível de serviço, podendo múltiplas requisições acessar a mesma instância
  • Há necessidade de trabalhar com mecanismos de bloqueio
  • O throughput está condicionado aos bloqueios realizados

Conclusão: Este artigo mostrou as características de cada um dos modos de gerenciamento de concorrência que existe dentro do WCF. Apesar da configuração ser extremamente básica, isso muda drasticamente o comportamento do serviço, principalmente quando combinado com algum dos modos de gerenciamento de instância. Além disso, a escolha do modo de concorrência influenciará em como você deve escrever a implementação do serviço, se atentando ou não aos possíveis bloqueios que possam acontecer quando tentar acessar recurso compartilhado.

Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.