【问题标题】:WCF Retry ProxyWCF 重试代理
【发布时间】:2013-04-15 14:32:29
【问题描述】:

我正在努力寻找实现 WCF 重试的最佳方法。我希望使客户体验尽可能干净。我知道有两种方法(见下文)。我的问题是:“我是否缺少第三种方法?也许这是普遍接受的方法?

方法#1:创建一个实现服务接口的代理。对于对代理的每次调用,实现重试。

public class Proxy : ISomeWcfServiceInterface
{
    public int Foo(int snurl)
    {
        return MakeWcfCall<int>(() => _channel.Foo(snurl));
    }

    public string Bar(string snuh)
    {
        return MakeWcfCall<string>(() => _channel.Bar(snuh));
    }

    private static T MakeWcfCall<T>(Func<T> func)
    {
        // Invoke the func and implement retries.
    }
}

方法 #2:将 MakeWcfCall()(上)更改为 public,并让消费代码直接发送 func。

我不喜欢方法 #1 的一点是,每次界面更改时都必须更新 Proxy 类。

我不喜欢方法 #2 的一点是客户端必须将他们的调用包装在一个 func 中。

我错过了更好的方法吗?

编辑

我在此处发布了一个答案(见下文),基于已接受的答案,它为我指明了正确的方向。我想我会在答案中分享我的代码,以帮助某人完成我必须完成的工作。希望对您有所帮助。

【问题讨论】:

标签: c# wcf oop


【解决方案1】:

不要弄乱生成的代码,因为正如您所提到的,它将再次生成,因此任何自定义都将被覆盖。

我看到了两种方式:

  1. 为每个包含重试变体的代理编写/生成部分类。这很混乱,因为当代理更改时您仍然需要对其进行调整

  2. 制作一个自定义版本的 svcutil,它允许您生成一个派生自不同基类的代理,并将重试代码放在该基类中。这是一项相当大的工作,但可以完成并以稳健的方式解决问题。

【讨论】:

  • 我不清楚;没有生成的代码。上面显示的代理是我自己的类。没有生成代理,因为我正在处理一个拥有电线两端并且可以引用接口的系统。
【解决方案2】:

您可以实现通用代理,例如使用 Castle。这里有一篇好文章http://www.planetgeek.ch/2010/10/13/dynamic-proxy-for-wcf-with-castle-dynamicproxy/。这种方法将提供实现接口的用户对象,并且您将拥有一个负责通信的类

【讨论】:

  • +1 这看起来很有希望。这里需要拦截。说到这一点,我刚刚遇到了 RealProxy:msdn.microsoft.com/en-us/library/…。我也需要检查一下。
  • 如果 RealProxy 给您足够的灵活性,这是一个不错的选择
  • 如果您需要 Garath 提议的示例,您可以在此处查看我的答案:stackoverflow.com/questions/7085406/… 在 Intercept 的实现中,您将在此处添加重试逻辑。理论上,如果您使用此模式,您将重试 WCF 服务上的所有方法。
  • @PhilPatterson 不错!感谢您的链接。如果 RealProxy(.NET 类)不起作用,则您的链接看起来像是要走的路。
  • @BobHorn 看来您可以使用 RealProxy 类创建相同的模式(这将具有不对 Castle 进行外部依赖的优势。对于传入的类型,您可能希望创建一个暴露 WCF 契约和 IClientChannel 接口的类或接口,以便您可以访问底层通信方法。此时使用 DyanmicProxy 或 RealProxy 只是一个实现细节。当然有多种方法可以实现这一点,希望你能找到灵感会导致您乐于支持的方法。
【解决方案3】:

通过方法 1,然后将所有上下文事件(打开、打开、故障...)包装到要由您的类代理公开的事件中,一旦通信发生故障,然后重新创建代理或调用一些递归方法在代理类里面。我可以和你分享一些我刚刚测试过的炒锅。

    public class DuplexCallBackNotificationIntegrationExtension : IExtension, INotificationPusherCallback {
    #region - Field(s) -
    private static Timer _Timer = null;
    private static readonly object m_SyncRoot = new Object();
    private static readonly Guid CMESchedulerApplicationID = Guid.NewGuid();
    private static CancellationTokenSource cTokenSource = new CancellationTokenSource();
    private static CancellationToken cToken = cTokenSource.Token;
    #endregion

    #region - Event(s) -
    /// <summary>
    /// Event fired during Duplex callback.
    /// </summary>
    public static event EventHandler<CallBackEventArgs> CallBackEvent;

    public static event EventHandler<System.EventArgs> InstanceContextOpeningEvent;
    public static event EventHandler<System.EventArgs> InstanceContextOpenedEvent;
    public static event EventHandler<System.EventArgs> InstanceContextClosingEvent;
    public static event EventHandler<System.EventArgs> InstanceContextClosedEvent;
    public static event EventHandler<System.EventArgs> InstanceContextFaultedEvent;
    #endregion

    #region - Property(ies) -
    /// <summary>
    /// Interface extension designation.
    /// </summary>
    public string Name {
        get {
            return "Duplex Call Back Notification Integration Extension.";
        }
    }

    /// <summary>
    /// GUI Interface extension.
    /// </summary>
    public IUIExtension UIExtension {
        get {
            return null;
        }
    }
    #endregion

    #region - Constructor(s) / Finalizer(s) -
    /// <summary>
    /// Initializes a new instance of the DuplexCallBackNotificationIntegrationExtension class.
    /// </summary>
    public DuplexCallBackNotificationIntegrationExtension() {
        CallDuplexNotificationPusher();
    }
    #endregion

    #region - Delegate Invoker(s) -

    void ICommunicationObject_Opening(object sender, System.EventArgs e) {
        DefaultLogger.DUPLEXLogger.Info("context_Opening");
        this.OnInstanceContextOpening(e);
    }

    void ICommunicationObject_Opened(object sender, System.EventArgs e) {
        DefaultLogger.DUPLEXLogger.Debug("context_Opened");
        this.OnInstanceContextOpened(e);
    }

    void ICommunicationObject_Closing(object sender, System.EventArgs e) {
        DefaultLogger.DUPLEXLogger.Debug("context_Closing");
        this.OnInstanceContextClosing(e);
    }

    void ICommunicationObject_Closed(object sender, System.EventArgs e) {
        DefaultLogger.DUPLEXLogger.Debug("context_Closed");
        this.OnInstanceContextClosed(e);
    }

    void ICommunicationObject_Faulted(object sender, System.EventArgs e) {
        DefaultLogger.DUPLEXLogger.Error("context_Faulted");
        this.OnInstanceContextFaulted(e);

        if (_Timer != null) {
            _Timer.Dispose();
        }

        IChannel channel = sender as IChannel;
        if (channel != null) {
            channel.Abort();
            channel.Close();
        }

        DoWorkRoutine(cToken);
    }

    protected virtual void OnCallBackEvent(Notification objNotification) {
        if (CallBackEvent != null) {
            CallBackEvent(this, new CallBackEventArgs(objNotification));
        }
    }

    protected virtual void OnInstanceContextOpening(System.EventArgs e) {
        if (InstanceContextOpeningEvent != null) {
            InstanceContextOpeningEvent(this, e);
        }
    }

    protected virtual void OnInstanceContextOpened(System.EventArgs e) {
        if (InstanceContextOpenedEvent != null) {
            InstanceContextOpenedEvent(this, e);
        }
    }

    protected virtual void OnInstanceContextClosing(System.EventArgs e) {
        if (InstanceContextClosingEvent != null) {
            InstanceContextClosingEvent(this, e);
        }
    }

    protected virtual void OnInstanceContextClosed(System.EventArgs e) {
        if (InstanceContextClosedEvent != null) {
            InstanceContextClosedEvent(this, e);
        }
    }

    protected virtual void OnInstanceContextFaulted(System.EventArgs e) {
        if (InstanceContextFaultedEvent != null) {
            InstanceContextFaultedEvent(this, e);
        }
    }
    #endregion

    #region - IDisposable Member(s) -

    #endregion

    #region - Private Method(s) -

    /// <summary>
    /// 
    /// </summary>
    void CallDuplexNotificationPusher() {
        var routine = Task.Factory.StartNew(() => DoWorkRoutine(cToken), cToken);
        cToken.Register(() => cancelNotification());
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="ct"></param>
    void DoWorkRoutine(CancellationToken ct) {
        lock (m_SyncRoot) {
            var context = new InstanceContext(this);
            var proxy = new NotificationPusherClient(context);
            ICommunicationObject communicationObject = proxy as ICommunicationObject;
            communicationObject.Opening += new System.EventHandler(ICommunicationObject_Opening);
            communicationObject.Opened += new System.EventHandler(ICommunicationObject_Opened);
            communicationObject.Faulted += new System.EventHandler(ICommunicationObject_Faulted);
            communicationObject.Closed += new System.EventHandler(ICommunicationObject_Closed);
            communicationObject.Closing += new System.EventHandler(ICommunicationObject_Closing);


            try {
                proxy.Subscribe(CMESchedulerApplicationID.ToString());
            }
            catch (Exception ex) {
                Logger.HELogger.DefaultLogger.DUPLEXLogger.Error(ex);                    

                switch (communicationObject.State) {
                    case CommunicationState.Faulted:
                        proxy.Close();
                        break;

                    default:
                        break;
                }

                cTokenSource.Cancel();
                cTokenSource.Dispose();                    
                cTokenSource = new CancellationTokenSource();
                cToken = cTokenSource.Token;
                CallDuplexNotificationPusher();  
            }

            bool KeepAliveCallBackEnabled = Properties.Settings.Default.KeepAliveCallBackEnabled;
            if (KeepAliveCallBackEnabled) {
                _Timer = new Timer(new TimerCallback(delegate(object item) {
                    DefaultLogger.DUPLEXLogger.Debug(string.Format("._._._._._. New Iteration {0: yyyy MM dd hh mm ss ffff} ._._._._._.", DateTime.Now.ToUniversalTime().ToString()));
                    DBNotificationPusherService.Acknowledgment reply = DBNotificationPusherService.Acknowledgment.NAK;
                    try {
                        reply = proxy.KeepAlive();
                    }
                    catch (Exception ex) {
                        DefaultLogger.DUPLEXLogger.Error(ex);

                        switch (communicationObject.State) {
                            case CommunicationState.Faulted:
                            case CommunicationState.Closed:
                                proxy.Abort();
                                ICommunicationObject_Faulted(null, null);
                                break;

                            default:
                                break;
                        }
                    }

                    DefaultLogger.DUPLEXLogger.Debug(string.Format("Acknowledgment = {0}.", reply.ToString()));
                    _Timer.Change(Properties.Settings.Default.KeepAliveCallBackTimerInterval, Timeout.Infinite);
                }), null, Properties.Settings.Default.KeepAliveCallBackTimerInterval, Timeout.Infinite);
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    void cancelNotification() {
       DefaultLogger.DUPLEXLogger.Warn("Cancellation request made!!");
    }
    #endregion 

    #region - Public Method(s) -
    /// <summary>
    /// Fire OnCallBackEvent event and fill automatic-recording collection with newest 
    /// </summary>
    /// <param name="action"></param>
    public void SendNotification(Notification objNotification) {

        // Fire event callback.
        OnCallBackEvent(objNotification);
    }
    #endregion

    #region - Callback(s) -
    private void OnAsyncExecutionComplete(IAsyncResult result) {

    }
    #endregion
}

【讨论】:

    【解决方案4】:

    只需将所有服务调用包装在一个函数中,获取一个委托,该委托将在必要的时间内执行传递的委托

    internal R ExecuteServiceMethod<I, R>(Func<I, R> serviceCall, string userName, string password) {
    
        //Note all clients have the name Manager, but this isn't a problem as they get resolved        
        //by type
        ChannelFactory<I> factory = new ChannelFactory<I>("Manager");
        factory.Credentials.UserName.UserName = userName;
        factory.Credentials.UserName.Password = password;
    
        I manager = factory.CreateChannel();
        //Wrap below in a retry loop
        return serviceCall.Invoke(manager);
    }
    

    【讨论】:

      【解决方案5】:

      我做过这种事情,我使用 .net 的 RealProxy 类解决了这个问题。

      使用RealProxy,您可以使用您提供的接口在运行时创建实际代理。从调用代码来看,他们好像在使用IFoo 通道,但实际上它是代理的IFoo,然后您有机会拦截对任何方法、属性、构造函数等的调用……

      RealProxy派生,然后可以重写Invoke方法来拦截对WCF方法的调用并处理CommunicationException等。

      【讨论】:

      • +1 我认为这正是我想要的。让我研究一下,如果它像我认为的那样工作,那么这应该是公认的答案。谢谢!
      • 我得到了这个工作。我能够拦截 WCF 调用。现在我只需要实现重试机制。再次感谢!
      • @BobHorn,不客气!如果要集成到整个 WCF 通道创建过程中,可以从 ChannelFactory 派生并覆盖 CreateChannel。在 CreateChannel 中,您将使用您的 RealProxy 派生类来创建要返回的通道代理。
      • 您好,您在这里的需求基本上是如何实现“横切”关注点(重试,其他示例可能是日志记录)面向方面的编程解决了这个问题:en.wikipedia.org/wiki/Aspect-oriented_programming 这是实际上是一个非常干净的解决方案,让我建议看看 Unity 的拦截:msdn.microsoft.com/en-us/library/ff647107.aspx 和面向方面的编程msdn.microsoft.com/en-us/magazine/gg490353.aspx 如果您在代码的其他领域有这种担忧,并希望使这些行为可配置。干杯,
      • 任何带有完整源代码示例的最终解决方案?
      【解决方案6】:

      注意:这不应该是公认的答案,但我想发布解决方案以防它对其他人有所帮助。 Jim 的回答为我指明了这个方向。

      首先是消费代码,展示它是如何工作的:

      static void Main(string[] args)
      {
          var channelFactory = new WcfChannelFactory<IPrestoService>(new NetTcpBinding());
          var endpointAddress = ConfigurationManager.AppSettings["endpointAddress"];
      
          // The call to CreateChannel() actually returns a proxy that can intercept calls to the
          // service. This is done so that the proxy can retry on communication failures.            
          IPrestoService prestoService = channelFactory.CreateChannel(new EndpointAddress(endpointAddress));
      
          Console.WriteLine("Enter some information to echo to the Presto service:");
          string message = Console.ReadLine();
      
          string returnMessage = prestoService.Echo(message);
      
          Console.WriteLine("Presto responds: {0}", returnMessage);
      
          Console.WriteLine("Press any key to stop the program.");
          Console.ReadKey();
      }
      

      WcfChannelFactory:

      public class WcfChannelFactory<T> : ChannelFactory<T> where T : class
      {
          public WcfChannelFactory(Binding binding) : base(binding) {}
      
          public T CreateBaseChannel()
          {
              return base.CreateChannel(this.Endpoint.Address, null);
          }
      
          public override T CreateChannel(EndpointAddress address, Uri via)
          {
              // This is where the magic happens. We don't really return a channel here;
              // we return WcfClientProxy.GetTransparentProxy(). That class will now
              // have the chance to intercept calls to the service.
              this.Endpoint.Address = address;            
              var proxy = new WcfClientProxy<T>(this);
              return proxy.GetTransparentProxy() as T;
          }
      }
      

      WcfClientProxy:(这是我们拦截和重试的地方。)

          public class WcfClientProxy<T> : RealProxy where T : class
          {
              private WcfChannelFactory<T> _channelFactory;
      
              public WcfClientProxy(WcfChannelFactory<T> channelFactory) : base(typeof(T))
              {
                  this._channelFactory = channelFactory;
              }
      
              public override IMessage Invoke(IMessage msg)
              {
                  // When a service method gets called, we intercept it here and call it below with methodBase.Invoke().
      
                  var methodCall = msg as IMethodCallMessage;
                  var methodBase = methodCall.MethodBase;
      
                  // We can't call CreateChannel() because that creates an instance of this class,
                  // and we'd end up with a stack overflow. So, call CreateBaseChannel() to get the
                  // actual service.
                  T wcfService = this._channelFactory.CreateBaseChannel();
      
                  try
                  {
                      var result = methodBase.Invoke(wcfService, methodCall.Args);
      
                      return new ReturnMessage(
                            result, // Operation result
                            null, // Out arguments
                            0, // Out arguments count
                            methodCall.LogicalCallContext, // Call context
                            methodCall); // Original message
                  }
                  catch (FaultException)
                  {
                      // Need to specifically catch and rethrow FaultExceptions to bypass the CommunicationException catch.
                      // This is needed to distinguish between Faults and underlying communication exceptions.
                      throw;
                  }
                  catch (CommunicationException ex)
                  {
                      // Handle CommunicationException and implement retries here.
                      throw new NotImplementedException();
                  }            
              }
          }
      

      被代理拦截的调用时序图:

      【讨论】:

      • 我们如何管理消费代码块中的连接关闭/处置?不关闭会不会导致内存泄露问题?
      • 我认为这仅适用于同步调用。在异步调用合约(开始/结束)的情况下,异常只会被回调(结束动作)捕获;在这种情况下实施重试有什么想法吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-04
      • 2011-04-03
      • 2011-01-23
      • 2012-05-08
      • 1970-01-01
      • 2010-12-20
      • 1970-01-01
      相关资源
      最近更新 更多