WCF ChannelFactory 和 channel - 缓存、重用、关闭和恢复

标签 wcf dispose channel channelfactory recover

我的 WCF 客户端库有以下计划架构:

  • 使用 ChannelFactory 而不是 svcutil 生成的代理,因为
    我需要更多的控制权,而且我想把客户分开
    当我的 WCF 服务更改时组装并避免重新生成
  • 需要将带有消息检查器的行为应用于我的 WCF
    端点,因此每个 channel 都能够发送其
    自己的身份验证 token
  • 我的客户端库将从 MVC 前端使用,所以我必须考虑可能的线程问题
  • 我正在使用 .NET 4.5(也许它有一些帮助程序或新方法可以更好地实现 WCF 客户端?)

  • 我已经阅读了许多关于各种不同位的文章,但我仍然对如何以正确的方式将它们组合在一起感到困惑。我有以下问题:
  • 据我了解,建议将 ChannelFactory 缓存在静态变量中,然后从中获取 channel ,对吗?
  • 端点行为是特定于整个 ChannelFactory 还是我可以分别为每个 channel 应用我的身份验证行为?如果行为特定于整个工厂,这意味着我不能在端点行为对象中保留任何状态信息,因为相同的身份验证 token 将被每个 channel 重用,但显然我希望每个 channel 都有自己的身份验证 token 当前用户。这意味着,我必须在我的端点行为中计算 token (我可以将它保存在 HttpContext 中,我的消息检查器行为只会将它添加到传出消息中)。
  • 我的客户端类是一次性的(实现 IDispose)。我如何正确处置 channel ,知道它可能处于任何可能的状态(未打开、打开、失败......)?我只是丢弃它吗?我要中止它然后丢弃吗?我要关闭它(但它可能根本还没有打开)然后丢弃吗?
  • 如果在使用 channel 时遇到问题怎么办?是只有 channel 坏了还是整个 ChannelFactory 坏了?

  • 我猜,一行代码说一千多个单词,所以这是我的代码形式的想法。我已经用“???”标记了我上面的所有问题在代码中。
    public class MyServiceClient : IDisposable
    {
        // channel factory cache
        private static ChannelFactory<IMyService> _factory;
        private static object _lock = new object();
    
        private IMyService _client = null;
        private bool _isDisposed = false;
    
         /// <summary>
        /// Creates a channel for the service
        /// </summary>
        public MyServiceClient()
        {
            lock (_lock)
            {
                if (_factory == null)
                {
                    // ... set up custom bindings here and get some config values
    
                    var endpoint = new EndpointAddress(myServiceUrl);
                    _factory = new ChannelFactory<IMyService>(binding, endpoint);
    
                    // ???? do I add my auth behavior for entire ChannelFactory 
                    // or I can apply it for individual channels when I create them?
                }
            }
    
            _client = _factory.CreateChannel();
        }
    
        public string MyMethod()
        {
            RequireClientInWorkingState();
            try
            {
                return _client.MyMethod();
            }
            catch
            {
                RecoverFromChannelFailure();
                throw;
            }
        }
    
        private void RequireClientInWorkingState()
        {
            if (_isDisposed)
                throw new InvalidOperationException("This client was disposed. Create a new one.");
    
            // ??? is it enough to check for CommunicationState.Opened && Created?
            if (state != CommunicationState.Created && state != CommunicationState.Opened)
                throw new InvalidOperationException("The client channel is not ready to work. Create a new one.");
        }
    
        private void RecoverFromChannelFailure()
        {
            // ??? is it the best way to check if there was a problem with the channel?
            if (((IChannel)_client).State != CommunicationState.Opened)
            {
                // ??? is it safe to call Abort? won't it throw?
                ((IChannel)_client).Abort();
            }
    
            // ??? and what about ChannelFactory? 
            // will it still be able to create channels or it also might be broken and must be thrown away? 
            // In that case, how do I clean up ChannelFactory correctly before creating a new one?
        }
    
        #region IDisposable
    
        public void Dispose()
        {    
            // ??? is it how to free the channel correctly?
            // I've heard, broken channels might throw when closing 
            // ??? what if it is not opened yet?
            // ??? what if it is in fault state?
            try
            {
                ((IChannel)_client).Close();
            }
            catch
            {
               ((IChannel)_client).Abort();              
            }
    
            ((IDisposable)_client).Dispose();
    
            _client = null;
            _isDisposed = true;
        }
    
        #endregion
    }
    

    最佳答案

    我想晚一点再好一点……而且看起来作者已经在工作了,这可能对 future 的 WCF 用户有所帮助。

    1) ChannelFactory 安排包含 channel 所有行为的 channel 。通过 CreateChannel 方法创建 channel 会“激活” channel 。 channel 工厂可以被缓存。

    2)你用绑定(bind)和行为来塑造 channel 工厂。此形状与创建此 channel 的每个人共享。正如您在评论中指出的那样,您可以附加消息检查器,但更常见的情况是使用 Header 将自定义状态信息发送到服务。您可以通过 OperationContext.Current 附加 header

    using (var op = new OperationContextScope((IContextChannel)proxy))
    {
        var header = new MessageHeader<string>("Some State");
        var hout = header.GetUntypedHeader("message", "urn:someNamespace");
        OperationContext.Current.OutgoingMessageHeaders.Add(hout);
    }
    

    3)这是我处理客户端 channel 和工厂的一般方法(此方法是我的 ProxyBase 类的一部分)
    public virtual void Dispose()
    {
        CloseChannel();
        CloseFactory();
    }
    
    protected void CloseChannel()
    {
        if (((IChannel)_client).State == CommunicationState.Opened)
        {
            try
            {
                ((IChannel)_client).Close();
            }
            catch (TimeoutException /* timeout */)
            {
                // Handle the timeout exception
                ((IChannel)innerChannel).Abort();
            }
            catch (CommunicationException /* communicationException */)
            {
                // Handle the communication exception
                ((IChannel)_client).Abort();
            }
        }
    }
    
    protected void CloseFactory()
    {
        if (Factory.State == CommunicationState.Opened)
        {
            try
            {
                Factory.Close();
            }
            catch (TimeoutException /* timeout */)
            {
                // Handle the timeout exception
                Factory.Abort();
            }
            catch (CommunicationException /* communicationException */)
            {
                // Handle the communication exception
                Factory.Abort();
            }
        }
    }
    

    4) WCF 将故障 channel 而不是工厂。您可以实现重新连接逻辑,但这需要您从某些自定义 ProxyBase 例如创建和派生您的客户端。
    protected I Channel
    {
        get
        {
            lock (_channelLock)
            {
                if (! object.Equals(innerChannel, default(I)))
                {
                    ICommunicationObject channelObject = innerChannel as ICommunicationObject;
                    if ((channelObject.State == CommunicationState.Faulted) || (channelObject.State == CommunicationState.Closed))
                    {
                        // Channel is faulted or closing for some reason, attempt to recreate channel
                        innerChannel = default(I);
                    }
                }
    
                if (object.Equals(innerChannel, default(I)))
                {
                    Debug.Assert(Factory != null);
                    innerChannel = Factory.CreateChannel();
                    ((ICommunicationObject)innerChannel).Faulted += new EventHandler(Channel_Faulted);
                }
            }
    
            return innerChannel;
        }
    }
    

    5) 不要重复使用 channel 。打开,做某事,关闭是正常的使用模式。

    6)创建通用代理基类并从中派生所有客户端。这可能会有所帮助,例如重新连接、使用调用前/调用后逻辑、使用工厂中的事件(例如故障、打开)

    7)创建您自己的 CustomChannelFactory 这使您可以进一步控制工厂的行为方式,例如设置默认超时,强制执行各种绑定(bind)设置(MaxMessageSizes)等。
    public static void SetTimeouts(Binding binding, TimeSpan? timeout = null, TimeSpan? debugTimeout = null)
            {
                if (timeout == null)
                {
                    timeout = new TimeSpan(0, 0, 1, 0);
                }
                if (debugTimeout == null)
                {
                    debugTimeout = new TimeSpan(0, 0, 10, 0);
                }
                if (Debugger.IsAttached)
                {
                    binding.ReceiveTimeout = debugTimeout.Value;
                    binding.SendTimeout = debugTimeout.Value;
                }
                else
                {
                    binding.ReceiveTimeout = timeout.Value;
                    binding.SendTimeout = timeout.Value;
                }
            }
    

    关于WCF ChannelFactory 和 channel - 缓存、重用、关闭和恢复,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14547034/

    相关文章:

    java - 正确关闭 RabbitMQ channel 和连接

    go - 来自不同 channel 的数据进入 select 语句可以被忽略吗?

    c# - 重构 “procedural” WCF服务

    无需更改防火墙的 WCF SSL 安全传输或大负载

    c# - 我需要 Dispose 来注销事件吗?

    C# - Try/Catch/Finally 和使用正确的顺序

    android - Web 服务 session 管理

    Internet 上的 WCF 事务

    c# - 如果我的应用程序崩溃,Dispose() 会被调用吗?

    java - RabbitMQ channel 最佳实践