WCF通道和通道工厂缓存

23
所以我决定在我的WCF应用程序中提高性能,并尝试缓存通道和ChannelFactory。在开始之前,有两个问题需要澄清。
1)ChannelFactory应该实现为单例吗?
2)我对如何缓存/重用单个通道有些不确定。你有可以分享的示例吗?
重要的是要注意,我的WCF服务正在部署为独立应用程序,只有一个端点。
编辑:
谢谢回复。我还有几个问题...
1)我想知道缓存应该发生在哪里。我向我们公司的另一个部门提供使用此代码的客户端API。这个缓存发生在客户端上吗?
2)客户端API将作为Silverlight应用程序的一部分使用,这是否会改变任何内容?特别是,在这种情况下可用的缓存机制是什么?
3)我仍然不清楚GetChannelFactory方法的设计。如果我只有一个服务,那么是否应该创建并缓存一个ChannelFactory?
我仍然没有实施任何缓存功能(因为我对如何实现缓存感到非常困惑!),但是这是我为客户端代理编写的代码:
namespace MyCompany.MyProject.Proxies
{
    static readonly ChannelFactory<IMyService> channelFactory =
        new ChannelFactory<IMyService>("IMyService");

    public Response DoSomething(Request request)
    {
        var channel = channelFactory.CreateChannel();

        try
        {
            Response response = channel.DoSomethingWithService(request);
            ((ICommunicationObject)channel).Close();
            return response;
        }
        catch(Exception exception)
        {
            ((ICommenicationObject)channel).Abort();
        }
    }
}

对于第三点,是的,只需要创建一个通道工厂。基本上,每个服务端点都需要一个通道工厂。在我的情况下,我们已经有了大约6个,主要分布在两个层次之间。在你的情况下,如果你只会有一个服务,被一个应用程序使用,那么你可以简单地按照你上面所做的来做。你上面的代码走在了正确的轨道上。缓存将根据应用程序的需求进行。 - Tim
@Tim - 感谢你的所有帮助。我真的非常感激!我认为在我的情况下,我的服务非常“简单”,这导致我在查看其他有多个端点的示例时感到困惑。现在我不那么困惑了,因为Tim做了出色的解释!谢谢老兄! - Didaxis
非常荣幸能够帮上忙 - 祝您编码愉快! - Tim
3个回答

23
使用ChannelFactory创建工厂实例,然后缓存该实例。您可以根据需要/期望从缓存的实例中创建通信通道。
您是否需要多个通道工厂(即,是否有多个服务)?在我的经验中,这是您将看到性能最大收益的地方。创建通道是一项相当廉价的任务;开始设置需要时间。
我不会缓存单个通道 - 我会创建它们,用于操作,然后关闭它们。如果您将它们缓存,它们可能会超时,通道将出现故障,然后您必须中止它并创建一个新的通道。
不确定为什么要使用单例来实现ChannelFactory,特别是如果您要创建并缓存它,并且只有一个终结点。
稍后我有更多时间时,我会发布一些示例代码。
更新:代码示例
以下是我在工作中为项目实施此示例的方式。我使用了ChannelFactory<T>,因为我正在开发的应用程序是一个n层应用程序,其中包含几个服务,并且将添加更多服务。目标是简单地创建一次客户端,然后根据需要创建通信通道。这个想法的基本概念不是我的(我从网上的文章中得到的),尽管我为我的需求修改了实现。
我在应用程序中有一个静态帮助程序类,在该类中,我有一个字典和一个从通道工厂创建通信通道的方法。
字典如下(对象是值,因为它将包含不同的通道工厂,每个服务一个)。我在示例中放置了“Cache”作为占位符 - 用您正在使用的任何缓存机制替换语法。
public static Dictionary<string, object> OpenChannels
{
    get
    {
        if (Cache["OpenChannels"] == null)
        {
            Cache["OpenChannels"] = new Dictionary<string, object>();
        }

        return (Dictionary<string, object>)Cache["OpenChannels"];
    }
    set
    {
        Cache["OpenChannels"] = value;
    }
}

下面是一种从工厂实例创建通信通道的方法。该方法首先检查工厂是否存在-如果不存在,则创建并将其放入字典中,然后生成通道。否则,它只是从工厂的缓存实例生成通道。

public static T GetFactoryChannel<T>(string address)
{

    string key = typeof(T.Name);

    if (!OpenChannels.ContainsKey(key))
    {
        ChannelFactory<T> factory = new ChannelFactory<T>();
        factory.Endpoint.Address = new EndpointAddress(new System.Uri(address));
        factory.Endpoint.Binding = new BasicHttpBinding();
        OpenChannels.Add(key, factory);
    }

    T channel = ((ChannelFactory<T>)OpenChannels[key]).CreateChannel();

    ((IClientChannel)channel).Open();

    return channel;
}

我从工作中的示例中简化了这个例子。在这个方法中,你可以做很多事情 - 你可以处理多个绑定,为身份验证分配凭据等等。它几乎是生成客户端的一站式购物中心。

最后,在应用程序中使用它时,我通常创建一个通道,完成业务后关闭它(或者如果需要的话放弃它)。例如:

IMyServiceContract client;

try
{
    client = Helper.GetFactoryChannel<IMyServiceContract>("http://myserviceaddress");

    client.DoSomething();

    // This is another helper method that will safely close the channel, 
    // handling any exceptions that may occurr trying to close.
    // Shouldn't be any, but it doesn't hurt.
    Helper.CloseChannel(client);
}
catch (Exception ex)
{
    // Something went wrong; need to abort the channel
    // I also do logging of some sort here
    Helper.AbortChannel(client);
}

希望以上示例能够为您提供一些参考。我已经在生产环境中使用类似的内容大约一年了,效果非常好。我们遇到的99%的问题通常与应用程序外部的某些因素有关(或者是不受我们直接控制的外部客户端或数据源)。

如果有任何不清楚的地方或进一步的问题,请告诉我。


谢谢Tim。你提供了一些非常有价值的信息。我一定会关注你的示例! - Didaxis
@user384080 - 代码在我的答案中。如果不清楚,请告诉我。谢谢。 - Tim
1
@Tim,你的实现中有一个bug。无论地址是什么,你都会按合同类型缓存工厂。你应该有一个包含合同类型和地址的键。 - Anubis
异常处理似乎不够完善。您不知道是谁抛出了异常(可能是DoSomething),那么为什么决定中止通道而不是仅关闭它呢? - Cesar
@Cesar - 这是我在工作中使用的简化代码。在工作中,会做更多的事情,并且我在示例代码中包含了一个注释 // I also do logging of some sort here - Tim

6
您可以为每个WCF契约使您的ChannelFactory静态。需要注意的是,从.NET 3.5开始,代理对象由通道工厂进行资源池化以提高性能。 调用ICommunicationObject.Close()方法实际上将对象返回到池中以便重新使用。
如果您想进行一些优化,请查看分析器。如果您可以在代码中避免仅进行一次IO调用,那么它可能会比您使用通道工厂进行优化更加重要。不要选择要优化的区域,请使用分析器查找可以针对优化的位置。例如,如果您有一个SQL数据库,则可能会发现查询中存在易于优化的问题,如果这些问题尚未得到优化,则可以获得数量级的性能提升。

4

创建通道会影响性能。实际上,如果客户端使用ClientBase而不是纯ChannelFactory,WCF已经为ChannelFactory提供了缓存机制。但是,如果进行某些额外操作,缓存将过期(如果您想了解详情,请谷歌一下)。 对于ErOx的问题,我有另一个更好的解决方案。请参见以下内容:


namespace ChannelFactoryCacheDemo
{
    public static class ChannelFactoryInitiator
    {
        private static Hashtable channelFactories = new Hashtable();

        public static ChannelFactory Initiate(string endpointName)
        {
            ChannelFactory channelFactory = null;

            if (channelFactories.ContainsKey(endpointName))//already cached, get from the table
            {
                channelFactory = channelFactories[endpointName] as ChannelFactory;
            }
            else // not cached, create and cache then
            {
                channelFactory = new ChannelFactory(endpointName);
                lock (channelFactories.SyncRoot)
                {
                    channelFactories[endpointName] = channelFactory;
                }
            }
            return channelFactory;
        }
    }
    class AppWhereUseTheChannel
    {
        static void Main(string[] args)
        {
            ChannelFactory channelFactory = ChannelFactoryInitiator.Initiate("MyEndpoint");
        }
    }

    interface IMyContract { }
}

如果您有其他需求,可以自定义Initiate方法的逻辑和参数。但是这个初始化类不仅限于一个端点。它能够为应用程序中的所有端点提供强大的支持。希望它能为您正常工作。顺便说一下,这个解决方案不是我自己想出来的,我是从一本书中学到的。


请注意,lock 的使用不正确。应该在调用 ContainsKey 时也要获取锁。 - Luke Puplett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接