¿Cuál es la mejor solución para el problema del bloque "usando" del cliente WCF?

Resuelto Eric King asked hace 15 años • 26 respuestas

Me gusta crear instancias de mis clientes de servicio WCF dentro de un usingbloque, ya que es prácticamente la forma estándar de utilizar recursos que implementan IDisposable:

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

Pero, como se indica en este artículo de MSDN , empaquetar un cliente WCF en un usingbloque podría enmascarar cualquier error que provoque que el cliente quede en un estado de error (como un tiempo de espera o un problema de comunicación). En pocas palabras, cuando Dispose()se llama, el Close()método del cliente se activa, pero arroja un error porque se encuentra en un estado fallido. La excepción original queda entonces enmascarada por la segunda excepción. No es bueno.

La solución alternativa sugerida en el artículo de MSDN es evitar por completo el uso de un usingbloque y, en su lugar, crear una instancia de sus clientes y utilizarlos de esta manera:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

Comparado con el usingbloque, creo que es feo. Y mucho código para escribir cada vez que necesites un cliente.

Afortunadamente, encontré algunas otras soluciones, como ésta en el (ahora desaparecido) blog IServiceOriented. Empiezas con:

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 
    
    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
} 

Lo que luego permite:

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
}); 

Eso no está mal, pero no creo que sea tan expresivo y fácilmente comprensible como el usingbloque.

La solución que estoy intentando utilizar actualmente la leí por primera vez en blog.davidbarret.net . Básicamente, anulas el Dispose()método del cliente dondequiera que lo uses. Algo como:

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

Esto parece poder permitir el usingbloqueo nuevamente sin el peligro de enmascarar una excepción de estado fallida.

Entonces, ¿hay otros problemas a los que debo prestar atención al utilizar estas soluciones? ¿A alguien se le ha ocurrido algo mejor?

Eric King avatar Feb 22 '09 06:02 Eric King
Aceptado

En realidad, aunque escribí en un blog (ver la respuesta de Luke ), creo que esto es mejor que mi contenedor IDisposable. Código típico:

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
}); 

(editar por comentarios)

Dado que Uselos retornos son nulos, la forma más sencilla de manejar los valores de retorno es mediante una variable capturada:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated
Marc Gravell avatar Feb 21 '2009 23:02 Marc Gravell

Si puedo elegir entre la solución recomendada por IServiceOriented.com y la solución recomendada por el blog de David Barret , prefiero la simplicidad que ofrece anular el método Dispose() del cliente. Esto me permite seguir usando la instrucción usando() como se esperaría de un objeto desechable. Sin embargo, como señaló @Brian, esta solución contiene una condición de carrera en la que es posible que el estado no tenga un error cuando se verifica, pero podría tenerlo cuando se llama a Close(), en cuyo caso la excepción de comunicación aún ocurre.

Entonces, para solucionar este problema, he empleado una solución que combina lo mejor de ambos mundos.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}
Matt Davis avatar Sep 14 '2009 23:09 Matt Davis