2010-09-18 9 views
6

Estoy usando un netNamedPipeBinding para realizar la comunicación entre procesos de WCF desde una aplicación de Windows a un servicio de Windows.excepción WCF recibida al cerrar la conexión con devoluciones de llamada en uso

Ahora mi aplicación funciona bien en todas las demás cuentas (después de luchar contra mi parte justa de las excepciones WCF como cualquiera que haya trabajado con WCF lo sabría ...) pero este error es bastante resistente.

Para pintar una imagen de mi escenario: mi servicio de Windows podría ponerse en cola para hacer algún trabajo en un momento dado presionando un botón en la aplicación de Windows y luego hablar sobre el netNamedPipeBinding que es un enlace que admite devoluciones de llamada (dos comunicación directa) si no está familiarizado e inicia una solicitud para realizar este trabajo (en este caso, un procedimiento de carga de archivos) también lanza las devoluciones de llamada (eventos) cada pocos segundos, desde el progreso del archivo hasta la velocidad de transferencia, etc. a la aplicación de Windows, por lo que hay una integración bastante estrecha entre el cliente y el servidor; así es como recibo mi progreso de lo que se está ejecutando en mi servicio de Windows en mi aplicación de Windows.

Ahora, todo está bien, los dioses WCF están relativamente contentos conmigo en este momento, aparte de una desagradable excepción que recibo cada vez que apago la aplicación prematuramente (lo cual es un escenario perfectamente válido). Mientras que una transferencia está en curso, y las devoluciones de llamada están disparando bastante fuerte, recibo este error:

System.ServiceModel.ProtocolException: 
    The channel received an unexpected input message with Action 
    'http://tempuri.org/ITransferServiceContract/TransferSpeedChangedCallback' 
    while closing. You should only close your channel when you are not expecting 
    any more input messages. 

Ahora entiendo que el error, pero por desgracia no puedo garantizar que cerrar mi canal después de no recibir ningún messsages más de entrada, como se el usuario puede cerrar la aplicación en cualquier momento, por lo tanto, el trabajo continuará en el fondo del servicio de Windows (algo así como el funcionamiento de un escáner de virus). El usuario debe poder iniciar y cerrar la aplicación de herramientas de administración de ganancias tanto como lo desee sin interferencia.

Ahora el error, recibo inmediatamente después de realizar mi llamada Unsubscribe() que es la segunda última llamada antes de terminar la aplicación y lo que creo que es la forma preferida de desconectar un cliente WCF. Todo lo que cancela la suscripción antes de cerrar la conexión es simplemente eliminar la identificación del cliente de una matriz que se almacenó localmente en el servicio win service wcf (ya que esta es una instancia COMPARTIDA tanto por el servicio ganador como por la aplicación Windows; eventos programados por sí mismo) y después de la eliminación de la matriz de identificador de cliente que realizo, lo que espero (siento) debe ser una desconexión limpia.

El resultado de esto, además de recibir una excepción, es que mi aplicación se cuelga, la UI está en bloqueo total, barras de progreso y todo a mitad de camino, con todas las señales apuntando a tener una condición de carrera o punto muerto WCF [suspiro], pero ahora soy bastante experto en hilos y creo que esta es una situación relativamente aislada y leyendo la excepción tal como está, no creo que sea un tema 'hilo' per se, ya que establece más un problema de desconexión temprana que luego hace girar todos mis hilos en el caos, quizás causando el bloqueo.

Mi enfoque Unsubscribe() en el clientese parece a esto:

public void Unsubscribe() 
    { 
     try 
     { 
      // Close existing connections 
      if (channel != null && 
       channel.State == CommunicationState.Opened) 
      { 
       proxy.Unsubscribe(); 
      } 
     } 
     catch (Exception) 
     { 
      // This is where we receive the 'System.ServiceModel.ProtocolException'. 
     } 
     finally 
     { 
      Dispose(); 
     } 
    } 

Y mi método Dispose(), que debe realizar la desconexión limpia:

public void Dispose() 
    { 
     // Dispose object 
     if (channel != null) 
     { 
      try 
      { 
       // Close existing connections 
       Close(); 
       // Attempt dispose object 
       ((IDisposable)channel).Dispose(); 
      } 
      catch (CommunicationException) 
      { 
       channel.Abort(); 
      } 
      catch (TimeoutException) 
      { 
       channel.Abort(); 
      } 
      catch (Exception) 
      { 
       channel.Abort(); 
       throw; 
      } 
     } 
    } 

Y la contraparte servicio WCF Subscription() y la clase atributos (para referencia) en el servicio de Windows servidor (nada complicado aquí y se produce mi excepción lado del cliente):

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
    ConcurrencyMode = ConcurrencyMode.Multiple)] 
    public class TransferService : LoggableBase, ITransferServiceContract 
    { 
     public void Unsubscribe() 
     { 
      if (clients.ContainsKey(clientName)) 
      { 
       lock (syncObj) 
       { 
        clients.Remove(clientName); 
       } 
      } 

#if DEBUG 
      Console.WriteLine(" + {0} disconnected.", clientName); 
#endif 
     } 
     ... 
    } 

Interfaz de:

[ServiceContract(
    CallbackContract = typeof(ITransferServiceCallbackContract), 
    SessionMode = SessionMode.Required)] 
public interface ITransferServiceContract 
{ 
    [OperationContract(IsInitiating = true)] 
    bool Subscribe(); 

    [OperationContract(IsOneWay = true)] 
    void Unsubscribe(); 
    ... 
} 

Interfaz del contrato de devolución de llamada, que no hace nada muy emocionante, sólo llama a eventos a través de los delegados, etc. La razón por la que esto es incluido para mostrar usted mis atributos Hice aliviar una serie de callejones sin salida ya mediante la inclusión de UseSynchronizationContext = false:

[CallbackBehavior(UseSynchronizationContext = false, 
ConcurrencyMode = ConcurrencyMode.Multiple)] 
public class TransferServiceCallback : ITransferServiceCallbackContract 
{ ... } 

Realmente espero que alguien me puede ayudar! Muchas gracias = :)

+0

no sé sobre el tema específico, pero para información suena * * posiblemente debido a la forma en WCF utiliza sincronización de contexto (que es el medio de la forma en winforms etc) el bodrio de roscado. –

+0

Gracias Marc, sí, eso me sorprendió y alivié una serie de bloqueos al leer sobre ese tema, el truco fue establecer 'UseSynchronizationContext = false' en el contrato de devolución de llamada;) Añadiré esto a mis ejemplos. – GONeale

+0

ah, a la derecha; bueno verte ya lo tenía cubierto; p –

Respuesta

11

OH Dios mío, encontré el problema.

Esa excepción no tiene nada que ver con la suspensión de la aplicación Underyling, que era solo una excepción de precaución que puede atrapar con seguridad.

No lo creería, pasé unas 6 horas dentro y fuera de este error, resultó ser el channel.Close() esperando a que se completaran las solicitudes pendientes de WCF (¡que nunca terminaría hasta que la transferencia haya finalizado!) ¡derrota el propósito!)

Acabo de ir de línea de fuerza bruta línea tras línea, mi problema era si era demasiado lento ... nunca se colgaría, porque de alguna manera el canal estaría disponible para cerrar (incluso antes la transferencia había terminado) así que tuve que romper F5 y luego rápidamente dar un paso para atrapar el cable, y esa es la línea en la que terminó. Ahora simplemente aplico un valor de tiempo de espera a la operación Close() y lo capturo con un TimeoutException y luego aborto el canal si no se puede cerrar a tiempo.

ver el código fijo:

private void Close() 
{ 
    if (channel != null && 
     channel.State == CommunicationState.Opened) 
    { 
     // If cannot cleanly close down the app in 3 seconds, 
     // channel is locked due to channel heavily in use 
     // through callbacks or the like. 
     // Throw TimeoutException 
     channel.Close(new TimeSpan(0, 0, 0, 3)); 
    } 
} 

public void Dispose() 
{ 
    // Dispose object 
    if (channel != null) 
    { 
     try 
     { 
      // Close existing connections 
      // ***************************** 
      // This is the close operation where we perform 
      //the channel close and timeout check and catch the exception. 
      Close(); 

      // Attempt dispose object 
      ((IDisposable)channel).Dispose(); 
     } 
     catch (CommunicationException) 
     { 
      channel.Abort(); 
     } 
     catch (TimeoutException) 
     { 
      channel.Abort(); 
     } 
     catch (Exception) 
     { 
      channel.Abort(); 
      throw; 
     } 
    } 
} 

Estoy muy feliz de tener este error finalmente ha terminado y hecho con! Mi aplicación ahora se cierra sin problemas después de un tiempo de espera de 3 segundos, independientemente del estado actual del servicio WCF, espero haber podido ayudar a alguien más que alguna vez se encuentre sufriendo un problema similar.

Graham

+0

_Esa excepción no tenía nada que ver con la aplicación de Underyling, eso era solo una excepción de precaución que puede atrapar con seguridad._ ¿Tiene un enlace de referencia que habla de la seguridad de tragar ProtocolExceptions como este? Tengo exactamente el mismo problema. – lesscode

Cuestiones relacionadas