2010-08-02 5 views
10

Чтобы отточить некоторые примеры служб, которые будут использоваться в качестве ссылки для наших внутренних сценариев, я создал этот пример дуплексного канала WCF, собрав несколько примеров, найденных на протяжении многих лет.Ошибка обратного вызова WCF Duplex Ошибка

Дуплексная часть не работает, и я надеюсь, что мы сможем все это понять вместе. Мне не нравится размещать этот код, но я чувствую, что я сократил это настолько, насколько это возможно, поскольку WCF может идти, включая все части, которые я надеюсь получить от сообщества. Здесь могут быть какие-то действительно плохие идеи, я не говорю, что это правильно, это то, что у меня есть до сих пор.

Есть три части. Канал, Сервер и Клиент. Три проекта, и здесь три файла кода. Нет конфигурации XML, все кодируется. Далее следует выход кода.

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 ссылки канала, 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/Клиент .cs, ссылки канала, 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; 
     } 
    } 
} 

подборкой 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 

Как указал Андрей, проблема не настолько очевидна. Этот «обработанный вывод» не является желаемым выходом. Вместо этого я бы хотел, чтобы сервер работал, Pings и регистрация для успеха, а затем каждые 5 секунд сервер «Отправлял» HELLO? (#m) «синхронно» и сразу же Клиент будет преобразовывать и возвращать и что Сервер будет получать и распечатывать.

Вместо этого пинги работают, но с ошибкой обратного вызова с первой попытки попадает к клиенту при повторном подключении, но не возвращается на сервер, и все отключается.

Единственные исключения, которые я вижу, относятся к каналу, который ранее был поврежден и, следовательно, был непригодным для использования, но еще ничего о фактической ошибке, которая заставляет канал достичь этого состояния.

Я использовал аналогичный код с [OperationalBehavior(IsOneWay= true)] много раз. Странно, что этот, казалось бы, более распространенный случай дает мне такое горе.

Исключения, пойманное на стороне сервера, что я не понимаю, это:
System.TimeoutException: «Эта операция отправлен запрос schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous не получать ответ в течение установленного таймаута (00:00:00). Время, отведенное для этой операции, может быть частью более длительного таймаута. Возможно, это связано с тем, что служба все еще обрабатывает операцию или потому что услуга не может отправить ответьте на сообщение. Пожалуйста, подумайте об увеличении тайм-аута работы (путем перевода канала/прокси в IContextChannel и установки свойства OperationTimeout) и убедитесь, что служба может подключиться к клиенту ».

+1

Вы должны сказать, КАК дуплексная часть «не работает». В противном случае у кого-то есть мотивация прочитать код. –

+0

Хорошая точка. Я добавил комментарии внизу. Это лучше? –

+0

Может быть, вы должны поместить проект buildable и runnable в zip-файл где-нибудь в сети, чтобы идеал, у кого есть время, может построить, xcopy развернуть и запустить за 5 минут, а затем посмотреть в отладчике, что происходит, и настроить VS для того, чтобы копать в код. BTW StackOverfkow должен изменить свой CSS, чтобы начать использовать ограниченный интервал для кода :-) – ZXX

ответ

1

Это очень глупо/отягчающе, но кажется, что проблема ProtectionLevel.EncryptAndSign. Я нашел сообщение об ошибке в Google нечасто, связанное с привязками и Windows auth. Предложите мне догадаться, что, возможно, восходящее сообщение не работало из-за чего-то связанного с шифрованием привязки ... или чего-то еще. Но вместо этого он устанавливает значение ProtectionLevel.None, а вдруг позволяет дуплексному каналу работать на двухсторонние методы (методы, возвращающие значения обратно на сервер)

Я не говорю, что отключение уровня защиты - хорошая идея, но, по крайней мере, это значительный результат. Если вам нужны преимущества EncryptAndSign, вы можете продолжить исследование оттуда.

1

Это может не полностью исправить вашу проблему, но, глядя на ваш код, IDuplexSyncCallback определенно является подозреваемым.Часть его реализации сервиса на месте, но она также должна быть украшена ServiceContractAttribute. При обратном вызове он также должен быть обозначен как односторонний. Ниже приведен пример того, что я делал в прошлом для контракта обратного вызова и также могу помочь вам.

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

Даже в Образцах, на которые ссылается larsw, интерфейс обратного вызова НЕ отмечен [ServiceContract]. Я читал в другом месте, что это тоже не требуется, но я не могу найти этот URL снова. В любом случае, я попробовал просто не быть упрямым, и это не имеет значения. Второй вопрос IsOneWay = true не подходит для этого сценария - мне нужен ответ! Я не читал, что IsOneWay = true требуется для Callbacks (что они не могут вернуть значение) –

+0

OneWay не требуется, это правильно. Обратные вызовы обычно являются асинхронными, поэтому вы можете найти примеры таким образом. Извините, что это не помогло. – jlafay

+0

Спасибо, что посмотрели. –

3

На сервере в методе AnnounceSync добавить обработку FaultException и вы будете проинформированы, что нет ответа от сервера (в вашем случае является клиентом), что означает нет обратного вызова получено.

Это как вы предложили из-за таймаута. Меняем

binding.SendTimeout = TimeSpan.FromSeconds(3); 

Она будет работать, как ожидалось.

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

Ваша уверенность привлекательна, но я не могу заставить ее работать. Я добавил этот блок catch к server.cs, и я изменил тайм-аут с 2 секунд до 3. Нет изменений в поведении. Исключением является исключение TimeoutException, а не исключение FaultException. –

0

К сожалению, операции OneWay являются обязательным условием для дуплексных каналов.

+0

Нет, это неправда. Я собираюсь обновить ответ. –

Смежные вопросы