2010-08-02 20 views
10

En un esfuerzo por perfeccionar algunos ejemplos de servicios que se utilizarán como referencia para nuestros escenarios internos, he creado este ejemplo WCF Duplex Channel, reuniendo varios ejemplos encontrados a través de los años.WCF Duplex Callback Sample failure

La parte dúplex no funciona y espero que todos podamos resolverlo juntos. Odio publicar este código, pero creo que lo reduje todo lo corto que WCF puede llegar, al tiempo que incorporo todas las partes que espero hayan sido examinadas por la comunidad. Puede que haya algunas ideas realmente malas aquí. No digo que sea correcto, es solo lo que tengo hasta ahora.

Hay tres partes. El canal, el servidor y el cliente. Tres proyectos, y aquí, tres archivos de código. Sin configuración XML, todo está codificado. Seguido por el resultado del código.

Channel.proj/Channel.cs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.ServiceModel; 

namespace Channel 
{ 
    public interface IDuplexSyncCallback 
    { 
     [OperationContract] 
     string CallbackSync(string message, DateTimeOffset timestamp); 
    } 

    [ServiceContract(CallbackContract = typeof(IDuplexSyncCallback))] 
    public interface IDuplexSyncContract 
    { 
     [OperationContract] 
     void Ping(); 

     [OperationContract] 
     void Enroll(); 

     [OperationContract] 
     void Unenroll(); 
    } 
} 

Server.proj/Server.cs, referencias Canal, System.Runtime.Serialization, System.ServiceModel

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.ServiceModel; 
using System.Timers; 
using Channel; 
using System.Diagnostics; 
using System.Net.Security; 

namespace Server 
{ 
    class Program 
    { 
     // All of this just starts up the service with these hardcoded configurations 
     static void Main(string[] args) 
     { 
      ServiceImplementation implementation = new ServiceImplementation(); 
      ServiceHost service = new ServiceHost(implementation); 

      NetTcpBinding binding = new NetTcpBinding(SecurityMode.Transport); 
      binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows; 
      binding.Security.Mode = SecurityMode.Transport; 
      binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows; 
      binding.Security.Transport.ProtectionLevel = ProtectionLevel.EncryptAndSign; 
      binding.ListenBacklog = 1000; 
      binding.MaxConnections = 30; 
      binding.MaxReceivedMessageSize = 2147483647; 
      binding.ReaderQuotas.MaxStringContentLength = 2147483647; 
      binding.ReaderQuotas.MaxArrayLength = 2147483647; 
      binding.SendTimeout = TimeSpan.FromSeconds(2); 
      binding.ReceiveTimeout = TimeSpan.FromSeconds(10 * 60); // 10 minutes is the default if not specified 
      binding.ReliableSession.Enabled = true; 
      binding.ReliableSession.Ordered = true; 

      service.AddServiceEndpoint(typeof(IDuplexSyncContract), binding, new Uri("net.tcp://localhost:3828")); 

      service.Open(); 

      Console.WriteLine("Server Running ... Press any key to quit"); 
      Console.ReadKey(true); 

      service.Abort(); 
      service.Close(); 
      implementation = null; 
      service = null; 
     } 
    } 

    /// <summary> 
    /// ServiceImplementation of IDuplexSyncContract 
    /// </summary> 
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
     MaxItemsInObjectGraph = 2147483647, 
     IncludeExceptionDetailInFaults = true, 
     ConcurrencyMode = ConcurrencyMode.Multiple, 
     UseSynchronizationContext = false)] 
    class ServiceImplementation : IDuplexSyncContract 
    { 
     Timer announcementTimer = new Timer(5000); // Every 5 seconds 
     int messageNumber = 0; // message number incrementer - not threadsafe, just for debugging. 

     public ServiceImplementation() 
     { 
      announcementTimer.Elapsed += new ElapsedEventHandler(announcementTimer_Elapsed); 
      announcementTimer.AutoReset = true; 
      announcementTimer.Enabled = true; 
     } 

     void announcementTimer_Elapsed(object sender, ElapsedEventArgs e) 
     { 
      AnnounceSync(string.Format("HELLO? (#{0})", messageNumber++)); 
     } 

     #region IDuplexSyncContract Members 
     List<IDuplexSyncCallback> syncCallbacks = new List<IDuplexSyncCallback>(); 

     /// <summary> 
     /// Simple Ping liveness 
     /// </summary> 
     [OperationBehavior] 
     public void Ping() { return; } 

     /// <summary> 
     /// Add channel to subscribers 
     /// </summary> 
     [OperationBehavior] 
     void IDuplexSyncContract.Enroll() 
     { 
      IDuplexSyncCallback current = System.ServiceModel.OperationContext.Current.GetCallbackChannel<IDuplexSyncCallback>(); 

      lock (syncCallbacks) 
      { 
       syncCallbacks.Add(current); 

       Trace.WriteLine("Enrollment Complete"); 
      } 
     } 

     /// <summary> 
     /// Remove channel from subscribers 
     /// </summary> 
     [OperationBehavior] 
     void IDuplexSyncContract.Unenroll() 
     { 
      IDuplexSyncCallback current = System.ServiceModel.OperationContext.Current.GetCallbackChannel<IDuplexSyncCallback>(); 

      lock (syncCallbacks) 
      { 
       syncCallbacks.Remove(current); 

       Trace.WriteLine("Unenrollment Complete"); 
      } 
     } 

     /// <summary> 
     /// Callback to clients over enrolled channels 
     /// </summary> 
     /// <param name="message"></param> 
     void AnnounceSync(string message) 
     { 
      var now = DateTimeOffset.Now; 

      if (message.Length > 2000) message = message.Substring(0, 2000 - "[TRUNCATED]".Length) + "[TRUNCATED]"; 
      Trace.WriteLine(string.Format("{0}: {1}", now.ToString("mm:ss.fff"), message)); 

      lock (syncCallbacks) 
      { 
       foreach (var callback in syncCallbacks.ToArray()) 
       { 
        Console.WriteLine("Sending \"{0}\" synchronously ...", message); 

        CommunicationState state = ((ICommunicationObject)callback).State; 

        switch (state) 
        { 
         case CommunicationState.Opened: 
          try 
          { 
           Console.WriteLine("Client said '{0}'", callback.CallbackSync(message, now)); 
          } 
          catch (Exception ex) 
          { 
           // Timeout Error happens here 
           syncCallbacks.Remove(callback); 
           Console.WriteLine("Removed client"); 
          } 
          break; 
         case CommunicationState.Created: 
         case CommunicationState.Opening: 
          break; 
         case CommunicationState.Faulted: 
         case CommunicationState.Closed: 
         case CommunicationState.Closing: 
         default: 
          syncCallbacks.Remove(callback); 
          Console.WriteLine("Removed client"); 
          break; 
        } 
       } 
      } 
     } 
     #endregion 
    } 
} 

Client.proj/Cliente Cs, hace referencia a Canal, System.Runtime.Serialization, System.ServiceModel

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.ServiceModel; 
using System.Timers; 
using System.Diagnostics; 
using Channel; 
using System.Net; 

namespace Client 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      using (var callbackSyncProxy = new CallbackSyncProxy(new Uri("net.tcp://localhost:3828"), CredentialCache.DefaultNetworkCredentials)) 
      { 
       callbackSyncProxy.Faulted += (s, e) => Console.WriteLine("CallbackSyncProxy Faulted."); 
       callbackSyncProxy.ConnectionUnavailable += (s, e) => Console.WriteLine("CallbackSyncProxy ConnectionUnavailable."); 
       callbackSyncProxy.ConnectionRecovered += (s, e) => Console.WriteLine("CallbackSyncProxy ConnectionRecovered."); 

       callbackSyncProxy.Ping(); 
       callbackSyncProxy.Ping(); 
       callbackSyncProxy.Ping(); 

       Console.WriteLine("Pings completed. Enrolling ..."); 

       callbackSyncProxy.AnnouncementSyncHandler = AnnouncementHandler; 

       Console.WriteLine("Enrolled and waiting. Press any key to quit ..."); 
       Console.ReadKey(true); // Wait for quit 
      } 
     } 

     /// <summary> 
     /// Called by the server through DuplexChannel 
     /// </summary> 
     /// <param name="message"></param> 
     /// <param name="timeStamp"></param> 
     /// <returns></returns> 
     static string AnnouncementHandler(string message, DateTimeOffset timeStamp) 
     { 
      Console.WriteLine("{0}: {1}", timeStamp, message); 

      return string.Format("Dear Server, thanks for that message at {0}.", timeStamp); 
     } 
    } 

    /// <summary> 
    /// Encapsulates the client-side WCF setup logic. 
    /// 
    /// There are 3 events Faulted, ConnectionUnavailable, ConnectionRecovered that might be of interest to the consumer 
    /// Enroll and Unenroll of the ServiceContract are called when setting an AnnouncementSyncHandler 
    /// Ping, when set correctly against the server's send/receive timeouts, will keep the connection alive 
    /// </summary> 
    public class CallbackSyncProxy : IDisposable 
    { 
     Uri listen; 
     NetworkCredential credentials; 
     NetTcpBinding binding; 
     EndpointAddress serverEndpoint; 
     ChannelFactory<IDuplexSyncContract> channelFactory; 
     DisposableChannel<IDuplexSyncContract> channel; 

     readonly DuplexSyncCallback callback = new DuplexSyncCallback(); 

     object sync = new object(); 
     bool enrolled; 
     Timer pingTimer = new Timer(); 
     bool quit = false; // set during dispose 

     // Events of interest to consumer 
     public event EventHandler Faulted; 
     public event EventHandler ConnectionUnavailable; 
     public event EventHandler ConnectionRecovered; 

     // AnnouncementSyncHandler property. When set to non-null delegate, Enrolls client with server. 
     // passes through to the DuplexSyncCallback callback.AnnouncementSyncHandler 
     public Func<string, DateTimeOffset, string> AnnouncementSyncHandler 
     { 
      get 
      { 
       Func<string, DateTimeOffset, string> temp = null; 

       lock (sync) 
       { 
        temp = callback.AnnouncementSyncHandler; 
       } 
       return temp; 
      } 
      set 
      { 
       lock (sync) 
       { 
        if (callback.AnnouncementSyncHandler == null && value != null) 
        { 
         callback.AnnouncementSyncHandler = value; 

         Enroll(); 
        } 
        else if (callback.AnnouncementSyncHandler != null && value == null) 
        { 
         Unenroll(); 

         callback.AnnouncementSyncHandler = null; 
        } 
        else // null to null or function to function, just update it 
        { 
         callback.AnnouncementSyncHandler = value; 
        } 
       } 
      } 
     } 

     /// <summary> 
     /// using (var proxy = new CallbackSyncProxy(listen, CredentialCache.DefaultNetworkCredentials) { ... } 
     /// </summary> 
     public CallbackSyncProxy(Uri listen, NetworkCredential credentials) 
     { 
      this.listen = listen; 
      this.credentials = credentials; 

      binding = new NetTcpBinding(SecurityMode.Transport); 
      binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows; 
      binding.Security.Mode = SecurityMode.Transport; 
      binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows; 
      binding.MaxReceivedMessageSize = 2147483647; 
      binding.ReaderQuotas.MaxArrayLength = 2147483647; 
      binding.ReaderQuotas.MaxBytesPerRead = 2147483647; 
      binding.ReaderQuotas.MaxDepth = 2147483647; 
      binding.ReaderQuotas.MaxStringContentLength = 2147483647; 
      binding.ReliableSession.Enabled = true; 
      binding.ReliableSession.Ordered = true; 
      serverEndpoint = new EndpointAddress(listen); 

      pingTimer.AutoReset = true; 
      pingTimer.Elapsed += pingTimer_Elapsed; 
      pingTimer.Interval = 20000; 
     } 

     /// <summary> 
     /// Keep the connection alive by pinging at some set minimum interval 
     /// </summary> 
     void pingTimer_Elapsed(object sender, ElapsedEventArgs e) 
     { 
      bool locked = false; 

      try 
      { 
       locked = System.Threading.Monitor.TryEnter(sync, 100); 
       if (!locked) 
       { 
        Console.WriteLine("Unable to ping because synchronization lock could not be aquired in a timely fashion"); 
        return; 
       } 
       Debug.Assert(channel != null, "CallbackSyncProxy.channel is unexpectedly null"); 

       try 
       { 
        channel.Service.Ping(); 
       } 
       catch 
       { 
        Console.WriteLine("Unable to ping"); 
       } 
      } 
      finally 
      { 
       if (locked) System.Threading.Monitor.Exit(sync); 
      } 
     } 

     /// <summary> 
     /// Ping is a keep-alive, but can also be called by the consuming code 
     /// </summary> 
     public void Ping() 
     { 
      lock (sync) 
      { 
       if (channel != null) 
       { 
        channel.Service.Ping(); 
       } 
       else 
       { 
        using (var c = new DisposableChannel<IDuplexSyncContract>(GetChannelFactory().CreateChannel())) 
        { 
         c.Service.Ping(); 
        } 
       } 
      } 
     } 

     /// <summary> 
     /// Enrollment - called when AnnouncementSyncHandler is assigned 
     /// </summary> 
     void Enroll() 
     { 
      lock (sync) 
      { 
       if (!enrolled) 
       { 
        Debug.Assert(channel == null, "CallbackSyncProxy.channel is unexpectedly not null"); 

        var c = new DisposableChannel<IDuplexSyncContract>(GetChannelFactory().CreateChannel()); 

        ((ICommunicationObject)c.Service).Open(); 

        ((ICommunicationObject)c.Service).Faulted += new EventHandler(CallbackChannel_Faulted); 

        c.Service.Enroll(); 

        channel = c; 

        Debug.Assert(!pingTimer.Enabled, "CallbackSyncProxy.pingTimer unexpectedly Enabled"); 

        pingTimer.Start(); 

        enrolled = true; 
       } 
      } 
     } 

     /// <summary> 
     /// Unenrollment - called when AnnouncementSyncHandler is set to null 
     /// </summary> 
     void Unenroll() 
     { 
      lock (sync) 
      { 
       if (callback.AnnouncementSyncHandler != null) 
       { 
        Debug.Assert(channel != null, "CallbackSyncProxy.channel is unexpectedly null"); 

        channel.Service.Unenroll(); 

        Debug.Assert(!pingTimer.Enabled, "CallbackSyncProxy.pingTimer unexpectedly Disabled"); 

        pingTimer.Stop(); 

        enrolled = false; 
       } 
      } 
     } 

     /// <summary> 
     /// Used during enrollment to establish a channel. 
     /// </summary> 
     /// <returns></returns> 
     ChannelFactory<IDuplexSyncContract> GetChannelFactory() 
     { 
      lock (sync) 
      { 
       if (channelFactory != null && 
        channelFactory.State != CommunicationState.Opened) 
       { 
        ResetChannel(); 
       } 

       if (channelFactory == null) 
       { 
        channelFactory = new DuplexChannelFactory<IDuplexSyncContract>(callback, binding, serverEndpoint); 

        channelFactory.Credentials.Windows.ClientCredential = credentials; 

        foreach (var op in channelFactory.Endpoint.Contract.Operations) 
        { 
         var b = op.Behaviors[typeof(System.ServiceModel.Description.DataContractSerializerOperationBehavior)] as System.ServiceModel.Description.DataContractSerializerOperationBehavior; 

         if (b != null) 
          b.MaxItemsInObjectGraph = 2147483647; 
        } 
       } 
      } 

      return channelFactory; 
     } 

     /// <summary> 
     /// Channel Fault handler, set during Enrollment 
     /// </summary> 
     void CallbackChannel_Faulted(object sender, EventArgs e) 
     { 
      lock (sync) 
      { 
       if (Faulted != null) 
       { 
        Faulted(this, new EventArgs()); 
       } 

       ResetChannel(); 

       pingTimer.Stop(); 
       enrolled = false; 

       if (callback.AnnouncementSyncHandler != null) 
       { 
        while (!quit) // set during Dispose 
        { 
         System.Threading.Thread.Sleep(500); 

         try 
         { 
          Enroll(); 

          if (ConnectionRecovered != null) 
          { 
           ConnectionRecovered(this, new EventArgs()); 

           break; 
          } 
         } 
         catch 
         { 
          if (ConnectionUnavailable != null) 
          { 
           ConnectionUnavailable(this, new EventArgs()); 
          } 
         } 
        } 
       } 
      } 
     } 

     /// <summary> 
     /// Reset the Channel & ChannelFactory if they are faulted and during dispose 
     /// </summary> 
     void ResetChannel() 
     { 
      lock (sync) 
      { 
       if (channel != null) 
       { 
        channel.Dispose(); 
        channel = null; 
       } 

       if (channelFactory != null) 
       { 
        if (channelFactory.State == CommunicationState.Faulted) 
         channelFactory.Abort(); 
        else 
         try 
         { 
          channelFactory.Close(); 
         } 
         catch 
         { 
          channelFactory.Abort(); 
         } 

        channelFactory = null; 
       } 
      } 
     } 

     // Disposing of me implies disposing of disposable members 
     #region IDisposable Members 
     bool disposed; 
     void IDisposable.Dispose() 
     { 
      if (!disposed) 
      { 
       Dispose(true); 
      } 

      GC.SuppressFinalize(this); 
     } 

     void Dispose(bool disposing) 
     { 
      if (disposing) 
      { 
       quit = true; 

       ResetChannel(); 

       pingTimer.Stop(); 

       enrolled = false; 

       callback.AnnouncementSyncHandler = null; 
      } 

      disposed = true; 
     } 
     #endregion 
    } 

    /// <summary> 
    /// IDuplexSyncCallback implementation, instantiated through the CallbackSyncProxy 
    /// </summary> 
    [CallbackBehavior(UseSynchronizationContext = false, 
    ConcurrencyMode = ConcurrencyMode.Multiple, 
    IncludeExceptionDetailInFaults = true)] 
    class DuplexSyncCallback : IDuplexSyncCallback 
    { 
     // Passthrough handler delegates from the CallbackSyncProxy 
     #region AnnouncementSyncHandler passthrough property 
     Func<string, DateTimeOffset, string> announcementSyncHandler; 
     public Func<string, DateTimeOffset, string> AnnouncementSyncHandler 
     { 
      get 
      { 
       return announcementSyncHandler; 
      } 
      set 
      { 
       announcementSyncHandler = value; 
      } 
     } 
     #endregion 

     /// <summary> 
     /// IDuplexSyncCallback.CallbackSync 
     /// </summary> 
     [OperationBehavior] 
     public string CallbackSync(string message, DateTimeOffset timestamp) 
     { 
      if (announcementSyncHandler != null) 
      { 
       return announcementSyncHandler(message, timestamp); 
      } 
      else 
      { 
       return "Sorry, nobody was home"; 
      } 
     } 
    } 

    // This class wraps an ICommunicationObject so that it can be either Closed or Aborted properly with a using statement 
    // This was chosen over alternatives of elaborate try-catch-finally blocks in every calling method, or implementing a 
    // new Channel type that overrides Disposable with similar new behavior 
    sealed class DisposableChannel<T> : IDisposable 
    { 
     T proxy; 
     bool disposed; 

     public DisposableChannel(T proxy) 
     { 
      if (!(proxy is ICommunicationObject)) throw new ArgumentException("object of type ICommunicationObject expected", "proxy"); 

      this.proxy = proxy; 
     } 

     public T Service 
     { 
      get 
      { 
       if (disposed) throw new ObjectDisposedException("DisposableProxy"); 

       return proxy; 
      } 
     } 

     public void Dispose() 
     { 
      if (!disposed) 
      { 
       Dispose(true); 
      } 

      GC.SuppressFinalize(this); 
     } 

     void Dispose(bool disposing) 
     { 
      if (disposing) 
      { 
       if (proxy != null) 
       { 
        ICommunicationObject ico = null; 

        if (proxy is ICommunicationObject) 
         ico = (ICommunicationObject)proxy; 

        // This state may change after the test and there's no known way to synchronize 
        // so that's why we just give it our best shot 
        if (ico.State == CommunicationState.Faulted) 
         ico.Abort(); // Known to be faulted 
        else 
         try 
         { 
          ico.Close(); // Attempt to close, this is the nice way and we ought to be nice 
         } 
         catch 
         { 
          ico.Abort(); // Sometimes being nice isn't an option 
         } 

        proxy = default(T); 
       } 
      } 

      disposed = true; 
     } 
    } 
} 

Clasificadas outpu t:

>> Server Running ... Press any key to quit 
          Pings completed. Enrolling ... << 
      Enrolled and waiting. Press any key to quit ... << 
>> Sending "HELLO? (#0)" synchronously ... 
           CallbackSyncProxy Faulted. << 
        CallbackSyncProxy ConnectionRecovered. << 
>> Removed client 
>> Sending "HELLO? (#2)" synchronously ... 
        8/2/2010 2:47:32 PM -07:00: HELLO? (#2) << 
>> Removed client 

Como ha señalado Andrew, el problema no es tan evidente. Esta "salida intercalada" no es la salida deseada. En su lugar, me gustaría que el Servidor se ejecute, que los Pings y la inscripción tengan éxito, y luego, cada 5 segundos, el servidor "Enviaría" HOLA. (#m) "sincrónicamente" e inmediatamente el Cliente transformaría y devolvería y el Servidor recibiría e imprimiría.

En su lugar, los pings funcionan, pero la Devolución de llamada falla en el primer intento, llega al Cliente en la reconexión pero no regresa al Servidor, y todo se desconecta.

Las únicas excepciones que veo se relacionan con el canal que ha fallado previamente y, por lo tanto, no se puede usar, pero todavía no hay nada sobre la falla real que hace que el canal alcance ese estado.

He usado código similar con [OperationalBehavior(IsOneWay= true)] muchas veces. Es extraño que este caso aparentemente más común me esté causando tanta pena.

La excepción capturada en el lado del servidor, lo que no entiendo, es:
System.TimeoutException: "Esta operación solicitud enviada a schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous no lo hizo recibir una respuesta dentro del tiempo de espera configurado (00:00:00). El tiempo asignado a esta operación puede haber sido una porción de un tiempo de espera más largo. Esto puede deberse a que el servicio aún está procesando la operación o porque el servicio no pudo enviar un mensaje de respuesta. Considere aumentar el tiempo de espera de la operación (transfiriendo el canal/proxy a IContextChannel y estableciendo la propiedad OperationTimeout) y asegúrese de que el servicio pueda conectarse con el cliente. "

+1

Debería decir CÓMO la parte dúplex "no funciona". De lo contrario, no hay motivación para que nadie lea el código. –

+0

Buen punto. He agregado comentarios en la parte inferior. ¿Eso está mejor? –

+0

Quizás deberías poner un proyecto ejecutable y ejecutable en un archivo zip en algún lugar de la red para que, idealmente, quienquiera que tenga tiempo pueda construir, ejecutar e implementar xcopy en 5 minutos y luego ver en depuración lo que está sucediendo y tener asistencia de VS para indagar en el código. Por cierto, StackOverfkow necesita cambiar su CSS para comenzar a usar espaciado ajustado para el código :-) – ZXX

Respuesta

1

Es muy tonto/agravante, pero parece que el ProtectionLevel.EncryptAndSign es el problema. Encontré el mensaje de error en Google con poca frecuencia relacionado con los enlaces y la autenticación de Windows. Guíeme a adivinar que tal vez la comunicación ascendente no funcionaba debido a algo relacionado con el cifrado vinculante ... o algo así. Pero al configurarlo en ProtectionLevel.None, de repente, permite que el canal dúplex funcione para métodos bidireccionales (métodos que devuelven valores al servidor)

No estoy diciendo que desactivar el nivel de protección sea una buena idea, pero al menos es una ventaja significativa. Si necesita los beneficios de EncryptAndSign, puede investigar más a fondo desde allí.

1

Esto puede no solucionar su problema por completo, pero mirando su código, IDuplexSyncCallback es definitivamente un sospechoso.Parte de la implementación del servicio está en su lugar, pero también debe decorarse con el ServiceContractAttribute. Al realizar una devolución de llamada, también se debe designar como unidireccional. A continuación se muestra una muestra de lo que he hecho en el pasado para un contrato de devolución de llamada y puede ayudarlo también.

[ServiceContract] 
public interface IDuplexSyncCallback 
{ 
    [OperationContract(IsOneWay = true) 
    string CallbackSync(string message, DateTimeOffset timestamp); 
} 
+0

Incluso en las muestras a las que hace referencia larsw, la interfaz de devolución de llamada NO está marcada con [ServiceContract]. He leído en otro lugar que tampoco es obligatorio, pero no puedo encontrar esa URL nuevamente. En cualquier caso, lo intenté solo para no ser obstinado y no hace la diferencia. El segundo problema de IsOneWay = true no es apropiado para este escenario. ¡Necesito una respuesta! No he leído que IsOneWay = true es obligatorio en Callbacks (que no pueden devolver un valor) –

+0

OneWay no es obligatorio, eso es correcto. Las rellamadas generalmente son asincrónicas y es por eso que puedes encontrar ejemplos de esa manera. Lo siento, eso no ayudó. – jlafay

+0

Gracias por echar un vistazo. –

3

En un servidor en el método AnnounceSync añadir la manipulación y FaultException Usted sería informado de que no hay respuesta del servidor (que en su caso es un cliente) significa que no hay devolución de llamada recibida.

Es como sugirió debido al tiempo de espera. lo que el cambio

binding.SendTimeout = TimeSpan.FromSeconds(3); 

que iba a funcionar como se espera.

try 
{ 
    Console.WriteLine("Client said '{0}'",callback.CallbackSync(message, now)); 
} 
catch (FaultException fex) 
{ 
    syncCallbacks.Remove(callback); 
    Console.WriteLine("Failed to call Client because" + fex.Reason); 
    Console.WriteLine(fex.Message); 
} 
+1

Su confianza es atractiva, pero no puedo hacer que esto funcione. He agregado este bloque catch al server.cs y he cambiado el tiempo de espera de 2 segundos a 3. No hay cambio en el comportamiento. La excepción lanzada es una TimeoutException, no una FaultException. –

0

Desafortunadamente, las operaciones de OneWay son un requisito previo para los canales dúplex.

+0

No, eso no es cierto. Estoy a punto de actualizar con la respuesta. –