2

No uses using con clientes WCF

Cuando agregamos una referencia a un servicio WCF, se crea una clase proxy de servicio Windows Communication Foundation (WCF). Esta clase deriva de System.ServiceModel.ClientBase<TChannel> e implementa IDisposable, por lo que uno se viene arriba :) y cree que puede usarla dentro de una sentencia using garantizando de esta manera la limpieza automática de recursos sin que se genere ninguna excepción. Gran error.

El problema radica en que la implementación de Dispose de ClientBase<TChannel> llama incondicionalmente al método Close. Este es su código.

void IDisposable.Dispose()
{
    this.Close();
}

El método Close provoca que el objeto pase a estado cerrado, pero si el estado del cliente es Faulted lanzará una excepción CommunicationObjectFaultedException. Es cierto que un método Dispose no debería lanzar excepciones y rompe con la guía de diseño del framework , pero ocurre.

En la descripción de la enumeración CommunicationState de la MSDN vemos esta advertencia

No se cierra ningún objeto que esté en el estado Faulted, y podría contener recursos. El método Abort se debería utilizar para cerrar un objeto que ha producido un error. Si se llama a Close en un objeto en el estado Faulted, se inicia una CommunicationObjectFaultedException porque no se puede cerrar el objeto de manera estable

¿Y Cuando un objeto cambia al estado Faulted? un objeto puede cambiar su estado a Faulted cuando por alguna razón encuentra un error del que no pude recuperarse, por ejemplo si el extremo remoto anula la sesión, o se produce un error de protocolo. Normalmente suelen ser determinados errores de comunicación, aunque no todos los errores de comunicación hacen que el objeto cambie a un estado Faulted.

Por lo tanto, la utilización de un cliente WCF en el ámbito de un using puede provocar fugas de memoria o que la App se vuelva inestable y además (por si fuera poco) enmascarará las excepciones.

using(MyServiceClient client = new MyServiceClient())
{
  string data = client.GetStringData();
  throw new ApplicationException("Esta excepción será enmascarada.");

} //<-- Llamada a dispose  

El uso del bloque using implica que en el cierre del mismo se llame automáticamente a Dispose. Si en el código anterior el estado del cliente se vuelve en algún momento Faulted, en el cierre del using se producirá una excepción. Esto provocará que la excepción ApplicationException sea enmascarada y en su lugar recibiremos una CommunicationObjectFaultedException, sin olvidar (faltaría mas) que no se liberarán los recursos del servicio.

Otra situación posible:

using(MyServiceClient client = new MyServiceClient())
{
  string data = client.GetStringData(); 

} //<-- Llamada a dispose  

//  si ha habido un error en el dispose el código después
//  del cierre de using no se ejecutará

En este caso lo que nos sucederá es que el código situado después del using no se ejecutará, lo que evidentemente es un problema. Por supuesto tampoco se liberarán los recursos.

El artículo de la MSDN Avoiding Problems with the Using Statement viene a explicar todo esto. Además nos propone como implementar la llamada a los servicios para asegurar la liberación de recursos.

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

El código anterior usa un cliente wcf, si se produjera un error en el bloque try, la excepción sería capturada por uno de los catch posteriores mediante el método abort(), que cerrará el canal liberando los recursos.

El tema es que no vamos a estar escribiendo este código cada vez que usamos un servicio, seria repetir demasiado código y nosotros no hacemos eso.

Una opción seria crearnos una partial class del servicio que implementara IDisposable.

public partial class MyServiceClient :IDisposable
{
    public void Dispose()
    {
        bool success = false;

        try {
            if (this.State != CommunicationState.Faulted)
            {
                this.Close();
                success = true;
            }
        }
        finally
        {
            if (success)
                this.Abort();
        }
    }
}

De esta manera podríamos utilizar el cliente wcf con un bloque using y cuando este llamara a Dispose invocaría a nuestro método, asegurándonos de que los recursos serán liberados y de que no se lanzará ninguna excepción, librándonos ya de paso de la enmascaración de nuestras excepciones.

Podríamos dejarlo aquí pero aun estaríamos escribiendo demasiado, si en nuestro proyecto tenemos varios servicios, tendríamos que crear una clase parcial para cada uno de ellos. Por mucho que encapsuláramos en otra clase el código de nuestro dispose para reducir y reutilizar código, no nos quedaría otra opción que la de crear para cada clase proxy una clase parcial. Aun y así no es mala opción. Pero … ¿porque no crearnos nuestro wrapper genérico de servicios?

public class Service<T>
{
    public static void Use<T>(Action<T> action)
    {
        ChannelFactory<T> channelFactory = new ChannelFactory<T>("*");
        T client = channelFactory.CreateChannel();

        bool success = false;
        try
        {
            action(client);
            ((IClientChannel)client).Close();
            channelFactory.Close();
            success = true;
        }
        catch (CommunicationException ex)
        {
          //código.
        }
        catch (TimeoutException ex)
        {
          //código
        }
        catch (Exception ex)
        {
          //código
        }
        finally
        {
            if (!success)
            {
                ((IClientChannel)client).Abort();
                channelFactory.Abort();
            }
        }
    }
}

Se hace uso de la clase ChannelFactory para crear y administrar los canales del cliente que indicaremos mediante genérico. Es una versión básica y funcional que permite el uso de cualquier clase proxy de servicios asegurando la liberación de recursos. Por ejemplo, se podrían añadir credenciales al canal u otras implementaciones como añadir logs, controlar mas excepciones, etc.. Para más información de la clase ChannelFactory visitar su referencia en la MSDN.

El uso de este wrapper es sencillo y elegante.

string data = null;

Service<MyServiceClient>.Use(client =>
{
   data = client.GetStringData();
});

 

2 Comments

  1. Estoy probando la clase Service genérica antes tenia mi clase pero utilizo public delegate void UseServiceDelegate(T proxy);
    al probar Service usando en Use( Action … me genera un error en el cliente, este es el codigo en el cliente:
    Usuario oUsuario = null;
    ServiceProxy.Use(client =>
    {
    oUsuario = client.UsuarioGetByName(paramLoginID)();
    });
    me indica el mensaje de error:
    Error 4 Los argumentos de tipo para el método ‘Contador.Infrastructure.Service.ServiceProxy.Use(System.Action)’ no se pueden inferir a partir del uso. Intente especificar los argumentos de tipo explícitamente. D:\Prism\ProyectoGuia\Contador\Contador.Modules.Seguridad\ViewModels\LoginViewModel.cs 92 17 Contador.Modules.Seguridad

    Anes al usar UseServiceDelegate(T proxy) no me genera ningun error y el cliente lo llamo:
    Usuario oUsuario = null;
    ServiceProxy.Use(c => oUsuario = c.UsuarioGetByName(paramLoginID));

    como puedo arreglar para que funcione tu propuesta de servicio generico
    Agradeceria tu colaboracion

    • Hola Ricardo,
      Te falta añadirle el tipo generico a serviceProxy.
      Ademas la siguiente linea es incorrecta
      oUsuario = client.UsuarioGetByName(paramLoginID)();

      Tienes dos veces los paréntesis para introducir los parámetros del método. Prueba con

      ServiceProxy< TIPO_GENERICO >.Use(client =>
      {
      oUsuario = client.UsuarioGetByName(paramLoginID);
      });

      Espero haberte ayudado.
      Saludos,
      Jose Martínez

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *