2010-08-12 19 views
18

Renovar la recompensa OTRA VEZ porque realmente necesito saber cómo hacer que funcione, o una respuesta definitiva sobre por qué no lo hará.Seeking WCF Duplex "TwoWay" Subscribe + Callback Example

I've added an alternative explanation of the problem here.

Tener un montón de tiempo para conseguir una de dos vías (IsOneWay = false) WCF cliente-servidor para trabajar en .Net 3/3.5.

Después de que el cliente se inscribe exitosamente con el servicio, el anuncio periódico del servicio() llama a los clientes inscritos. Ahora es cuando el cliente o el servidor se cuelga hasta que transcurre el SendTimeout del servidor, ajustado a 2 segundos. Luego, el lado del servidor tiene una excepción de tiempo de espera de la siguiente manera. Solo entonces el código de usuario del cliente RECIBE INMEDIATAMENTE LA LLAMADA DE MÉTODO e intenta devolver un valor. Para entonces, el socket del cliente se cancela y las cosas WCF fallan.

Me parece que algo en el cliente está bloqueando su cola local de WCF desde el procesamiento hasta que el socket agota el tiempo de espera, pero no lo suficientemente temprano como para cancelar la llamada al método local. Pero si se debe creer la excepción siguiente, el servidor está intentando enviar una operación al (¡inapropiado!) Y está expirando. Tal vez ese URI es solo el "Nombre" del cliente conectado remotamente, ya que WCF sabe que debe referirse a él a los fines del mensaje de error y simplemente parece indicar que no está cargando un URI. No puedo decir si el servidor falla primero o si el cliente falla primero.

He intentado agregar el seguimiento de WCF, pero no recibo mucha más información.

Similar sample code is here, pero debe haber sido demasiado para digerir. He experimentado con variaciones de ese código.

TimeoutException 'This request operation sent to http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous did not receive a reply within the configured timeout (00:00:00). The time allotted to this operation may have been a portion of a longer timeout. This may be because the service is still processing the operation or because the service was unable to send a reply message. Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.' 

Server stack trace: 
    at System.ServiceModel.Dispatcher.DuplexChannelBinder.SyncDuplexRequest.WaitForReply(TimeSpan timeout) 
    at System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(Message message, TimeSpan timeout) 
    at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout) 
    at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) 
    at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) 
+0

Esto es para 3.0/3.5, no 4.0. Gracias. –

+1

¿ha utilizado la localización wcf tanto en el cliente como en el servidor para ver si aparece algo? –

+0

Sí, como mejor puedo interpretar los resultados, solo encuentro la misma excepción que se menciona arriba. Todos los pedidos y mensajes parecen correctos de lo contrario. –

Respuesta

27

En primer lugar se consigue una copia de Programming WCF Services, si no tiene ya uno.

Si el cliente es WinForm o WPF, necesita usar [CallbackBehavior(UseSynchronizationContext = false)] ya que de lo contrario el cliente no procesará el mensaje entrante hasta que el hilo de la UI ingrese al bucle de mensajes.

En primer lugar, un canal "Duplex" en WCF no es verdaderamente Duplex. Un mensaje de

  • de cliente a servidor
  • Puede bloquear un mensaje al servidor está esperando desde el cliente
  • (o al revés)

Como sólo los mensajes se envían en orden en un solo canal WCF. Un canal WCF dúplex NO le proporciona dos colas de mensajes entrantes. Los resultados provenientes de una llamada "TwoWay" son exactamente los mismos que la "llamada" como este nivel de la pila WCF. Una vez que entiende esto, muchos de los problemas se vuelven más claros de comprender.

Si el cliente es WinForm o WPF, puede necesitar usar [CallbackBehavior(UseSynchronizationContext = false)] ya que de lo contrario el cliente no procesará el mensaje entrante hasta que el hilo de la UI ingrese al bucle de mensajes.

Algunas reglas que encontré para ayudar a evitar interbloqueos. (¡Mira mis preguntas WCF para ver el dolor que tuve!)

Sever nunca se debe llamar a un cliente en la misma conexión como una llamada desde el mismo cliente se encuentra en proceso de .

y/o

El cliente nunca debe volver a llamar al servidor en la misma conexión que es utilizado para las devoluciones de llamada “”, mientras que procesar una devolución de llamada.

La próxima vez creo que usaré dos contratos (y por lo tanto conexiones TCP) uno para la devolución de llamada y otro para todas las solicitudes de cliente-> servidor. O use mi propio sistema de votación, ya que esto me dio tanto dolor.

Lo siento, hoy no tengo tiempo para escribir un ejemplo. De todos modos, la mayoría de los ejemplos funcionan para lo que el ejemplo intenta hacer, pero se descomponen en la vida real por alguna razón que ver con su aplicación.

El mejor sitio web que conozco para ejemplos de WCF es Juval Lowy’s web site.

Su también puede encontrar el questions I asked about WCF on Stack Overflow útil, ya que yo estaba teniendo el mismo tipo de problemas que usted.

También pasar un día o dos leyendo todas las preguntas y respuestas de WCF en Stack Overflow dará una buena idea de los problemas que debe evitar.

+0

Hola Ian, gracias por todos estos consejos. El código de la pregunta SO relacionada tiene UseSynchronizationContext = false y no devuelve una llamada en el contexto del procesamiento de una llamada: el cliente se inscribe en las suscripciones, y luego, el servidor le hace a cada cliente una pregunta simple cada Timer.Tick. Voy a seguir mirando sus enlaces sugeridos también. –

+0

El término correcto para "no verdaderamente dúplex" es "semidúplex". –

+0

¡Muchas gracias por una explicación detallada! :) – Ross

4

Suponiendo que el cliente es una aplicación de WinForms, debe hacer que el manejo de la devolución de llamada sea independiente del resto de la aplicación utilizando la sugerencia de Ian además de delegar el trabajo a realizar en el hilo de la interfaz de usuario si es necesario. Por ejemplo, si el servidor quiere notificar al cliente de algo, como por ejemplo cambiar el texto de una etiqueta, se podría hacer lo siguiente:

[CallbackBehavior(UseSynchronizationContext = false)] 
internal class ServiceCallback : IServiceCallback 
{ 
    ChangeMainFormLabel(string text) 
    { 
     frmMain.Instance.BeginInvoke(new Action()(() => frmMain.Instance.lblSomething.Text = text)); 
    } 
} 

(Instance es una propiedad estática que devuelve la instancia única de frmMain y lblSomething es un Label que el servidor desea cambiar). Este método volverá inmediatamente y liberará al servidor de la interfaz de usuario del cliente, y la interfaz de usuario se actualizará tan pronto como sea posible. Y lo mejor de todo, no hay puntos muertos, ya que nadie está esperando a nadie.

+0

Es una aplicación de consola. Intenté ejecutar el ciclo principal en un hilo de trabajo, pero no pareció cambiar nada. ¿Todo eso encaja con tus ideas? –

+1

@ uosɐs: si se trata de una aplicación de consola, todo lo que tiene que hacer es usar 'UseSynchronizationContext = false'. Solo recuerda que los métodos en el objeto de devolución de llamada se ejecutarán en un hilo diferente, así que sincroniza cualquier acceso a los objetos compartidos. Si publica una muestra de código, podría ayudarnos a identificar el problema que está teniendo. –

+0

[Aquí] (http://stackoverflow.com/questions/3392123/wcf-duplex-callback-sample-failing) es el ejemplo de código que tiene synccontext = false como sugiere.(Lo tengo en la publicación original, pero entiendo que es difícil de identificar) –

0

Lo siento, olvidé por completo el ejemplo (: - $).

Aquí está mi código para el servidor:
ISpotifyServer.cs

[ServiceContract(CallbackContract = typeof(ISpotifyCallback))] 
public interface ISpotifyService 
{ 
    [OperationContract(IsOneWay = true)] 
    void Login(string username, string password); 
} 

ISpotifyCallback.cs

[ServiceContract] 
public interface ISpotifyCallback 
{ 
    [OperationContract(IsOneWay = true)] 
    void OnLoginComplete(); 

    [OperationContract(IsOneWay = true)] 
    void OnLoginError(); 
} 

Program.cs

class Program 
{ 
    static void Main(string[] args) 
    { 

     using (ServiceHost host = new ServiceHost(typeof(SpotifyService))) 
     { 
      host.Open(); 

      Console.WriteLine("Service running."); 
      Console.WriteLine("Endpoints:"); 

      foreach (ServiceEndpoint se in host.Description.Endpoints) 
       Console.WriteLine(se.Address.ToString()); 


      Console.ReadLine(); 

      host.Close(); 
     } 
    } 
} 

AppData.xml

<?xml version="1.0" encoding="utf-8" ?> 
<configuration> 
    <system.serviceModel> 
    <behaviors> 
     <serviceBehaviors> 
     <behavior name="MetadataEnabledBehavior"> 
      <serviceMetadata /> 
      <serviceDebug includeExceptionDetailInFaults="True"/> 
     </behavior> 
     </serviceBehaviors> 
    </behaviors> 
    <services> 
     <service behaviorConfiguration="MetadataEnabledBehavior" name="SpotiServer.SpotifyService"> 
     <host> 
      <baseAddresses> 
      <add baseAddress="net.tcp://localhost:9821" /> 
      </baseAddresses> 
     </host> 
     <clear /> 
     <endpoint address="spotiserver" binding="netTcpBinding" 
      name="TcpEndpoint" contract="SpotiServer.ISpotifyService" 
      listenUriMode="Explicit"> 
      <identity> 
      <dns value="localhost"/> 
      <certificateReference storeName="My" storeLocation="LocalMachine" 
       x509FindType="FindBySubjectDistinguishedName" /> 
      </identity> 
     </endpoint> 
     <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" /> 
     </service> 
    </services> 
    </system.serviceModel> 
</configuration> 

Y para el cliente:
Program.cs

class Program 
{ 
    static void Main(string[] args) 
    { 
     InstanceContext context = new InstanceContext(new CallbackHandler()); 

     String username; 
     String password; 

     Console.Write("Username: "); 
     username = Console.ReadLine(); 

     Console.WriteLine("Password: "); 
     password = ReadPassword(); 

     SpotiService.SpotifyServiceClient client = new SpotiService.SpotifyServiceClient(context); 
     client.Login(username, password); 

     Console.ReadLine(); 
    } 

    private static string ReadPassword() 
    { 
     Stack<string> passbits = new Stack<string>(); 
     //keep reading 
     for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true)) 
     { 
      if (cki.Key == ConsoleKey.Backspace) 
      { 
       //rollback the cursor and write a space so it looks backspaced to the user 
       Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); 
       Console.Write(" "); 
       Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop); 
       passbits.Pop(); 
      } 
      else 
      { 
       Console.Write("*"); 
       passbits.Push(cki.KeyChar.ToString()); 
      } 
     } 
     string[] pass = passbits.ToArray(); 
     Array.Reverse(pass); 
     return string.Join(string.Empty, pass); 
    } 
} 

creo que eso es todo. I of cause también tiene una implementación de las interfaces, que (en el lado del cliente) imprime el resultado en la consola y en el servidor ejecuta "OnLoginComplete" si el nombre de usuario y la contraseña son correctos, de lo contrario ejecuta "OnLoginError". Avíseme si no funciona o si necesita ayuda para configurarlo.

+1

Hola Alxandr. Aprecio el tiempo que lleva publicar esto, pero IsOneWay = true es el problema. He tenido éxito con tales IsOneWay = verdaderos ejemplos. Es el IsOneWay = falso el desafío y el objetivo de esta pregunta. –

+0

¿Está utilizando sockets o http? – Alxandr

+0

Un canal nettcp –