BT

Os problemas do WCF e os blocos Using

por Jonathan Allen , traduzido por Carlos Mendonça em 20 Mar 2009 |

Clientes de um serviço WCF não podem ser utilizandos dentro de um bloco Using porque eles podem inesperadamente lançar uma exceção. E mesmo que você capture esta exceção, é possível que uma conexão fique aberta. Vamos analisar a história deste problema e algumas soluções propostas.

Os alicerces do gerenciamento de recursos do .NET são a interface IDisposable e o bloco Using. A menos de objetos CLR, o ciclo de vida de tudo no mundo .NET é gerenciado utilizando-se estas ferramentas. Então, a pergunta que vem a mente é como a Microsoft conseguiu fracassar com isso no framework WCF.

O primeiro problema com os clientes de serviços WCF é que os métodos Close/Dispose podem lançar uma exceção. Sendo uma violação clara dos Princípios de Projeto do Framework e dos contratos IDisposable, isso faz com que o método Dispose seja inseguro ao ser chamado de um block Finally.

Pior ainda, há uma chance de que os métodos Close/Dispose deixem uma conexão aberta se o método Abort não for chamado. Se muitas forem deixadas abertas, isso pode levar a aplicação a ter problemas de performance e instabilidades.

Em uma mensagem em um newsgroup que data de 2006, Brian McNamara nos conta a história real por trás da falha de projeto.

A interface ICommunicationObject (da qual ServiceHost, ClientBase, IChannel, IChannelFactory e IChannerListener derivam) sempre teve dois métodos para destruir um objeto: (a) Close e (b) Abort. A semântica sugere que se você quer desativar de modo graceful, chame Close. Do contrário, para desativar de modo ungraceful, chame Abort.

Como conseqüência, o método Close() recebe um Timeout, possui uma versão assíncrona (já que ele pode bloquear a execução do programa) e pode lançar exceções. As exceções documentadas que o método pode lançar são CommunicationException (da qual CommunicationObjectFaultedException é uma subclasse) e TimeoutException.

O método Abort(), por outro lado, não deveria bloquear a execução (ou lançar alguma exceção esperada) e, desta forma, não possui um parâmetro de timeout e nem uma versão assíncrona.

Estes dois conceitos seguiram firmes da introdução do Indigo até hoje. Até aqui, tudo bem.

Na sua encarnação original, ICommunicationObject : IDisposable. Sendo uma interface de marcação, achamos que seria útil notificar os usuários que eles deveriam liberar o objeto o mais rápido possível. Aí que os problemas começaram.

Até o Beta 1, nós tínhamos Dispose() == Abort(). Parte da razão era que o Dispose() deveria fazer o mínimo necessário para realizar a limpeza. Esse foi possivelmente a maior reclamação que ouvimos no Beta 1. Os usuários iriam colocar seus channels em um block using() e qualquer mensagem em cache esperando para ser enviada seria perdida. Transações não receberiam commit, sessões receberiam ACK etc.

Para atender este feedback nós mudamos o comportamento para que Dipose() ~= Close(). Nós sabíamos que lançar exceções causa problemas (algumas delas estão listadas neste tópico do newsgroup), então fizemos que o Dispose tentasse ser esperto. Isto é, se não estivéssemos no estado Opened, nós iríamos internamente chamar o Abort(). Isso sozinho tem seus próprios problemas, o principal sendo que você não pode inferir nada do ponto de vista de confiabilidade sobre o sistema. O Dispose ainda pode lançar exceções, mas ele não irá te notificar _sempre_ que alguma coisa errada aconteceu. Eventualmente tomamos a decisão de que precisávamos tirar o IDisposable do ICommunicationObject. Depois de muita discussão, o IDisposable permaneceu no ServiceHost e no ClientBase. A teoria era que para a maioria dos usuários, não há problemas de um método Dispose lançar exceções, pois eles preferem contar com a conveniência do bloco using() e da indicação que o objeto deve ser liberado o mais rápido possível. Você poderia questionar (e alguns de nós questionamos) que nós também deveríamos ter removido a interface destas duas classes, mas para bem ou para mal, é nesta situação que nós estamos hoje. É uma questão que nunca vai agradar a todos, então nos nossos exemplos do SDK nós precisamos apoiar melhores práticas, que são utilizar o paradigma try { Close } / catch { Abort }.

Alternativas

Steve Smith propôs um extension method chamado CloseConnection. Este método, chamado de um bloco Finally no lugar do Close, encapsula a lógica Close/Abort.

O usuário bog1978 do newsgroup sugere utilizar o suporte a expressões lambda do C# para criar sua próprioa estrutura tipo Using. Este método recebe um novo objeto cliente e um método anônimo com o mesmo código que um bloco Using normal teria.

Por fim, existe a classe WCF Service Proxy Helper do Erwyn Van Der Meer. Os usuários a criam ao invés das classes proxy normais e ela fará a coisa certa ao fechar a conexão. Assim que é criada, ela irá construir o proxy automaticamente, expondo-o através de uma propriedade somente de leitura.

Olá visitante

Você precisa cadastrar-se no InfoQ Brasil ou para enviar comentários. Há muitas vantagens em se cadastrar.

Obtenha o máximo da experiência do InfoQ Brasil.

Dê sua opinião

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão
Comentários da comunidade

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

Dê sua opinião

Conteúdo educacional

Feedback geral
Bugs
Publicidade
Editorial
InfoQ Brasil e todo o seu conteúdo: todos os direitos reservados. © 2006-2014 C4Media Inc.
Política de privacidade
BT