WCF Channel and ChannelFactory Caching
Asked Answered
W

3

24

So I've decided to up the performance a bit in my WCF application, and attempt to cache Channels and the ChannelFactory. There's two questions I have about all of this that I need to clear up before I get started.

1) Should the ChannelFactory be implemented as a singleton?

2) I'm kind of unsure about how to cache/reuse individual channels. Do you have any examples of how to do this you can share?

It's probably important to note that my WCF service is being deployed as a stand alone application, with only one endpoint.

EDIT:

Thank you for the responses. I still have a few questions though...

1)I guess I'm confused as to where the caching should occur. I'm delivering a client API that uses this code to another department in our company. Does this caching occur on the client?

2)The client API will be used as part of a Silverlight application, does this change anything? In particular, what caching mechanisms are available in such a scenario?

3)I'm still not clear about the design of the GetChannelFactory method. If I have only one service, should only one ChannelFactory ever be created and cached?

I still haven't implemented any caching feature (because I'm utterly confused about how it should be done!), but here's what I have for the client proxy so far:

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();
        }
    }
}
Wolfgang answered 20/10, 2011 at 20:19 Comment(3)
For #3, yes, only one channel factory should be created. Basically, you'll have one channel factory for each of service endpiont you have. In my case, we have about 6 so far, spread primarily across 2 tiers. In your case, if you're only ever going to have one service, use by one app, you can simply do what you're doing above. You're code above is on the right track. Caching will be based on the needs of the app.Hardboard
@Hardboard -thanks for all of your help. I really do appreciate it! I think in my case, the fact that my service was so "simple" was causing me confusion looking over these other examples where there were multiple endpoints. Me = less confused now = Tim did a great job explaining! Thanks dude!Wolfgang
You're quite welcome. Glad I could help - happy coding!Hardboard
H
24

Use the ChannelFactory to create an instance of the factory, then cache that instance. You can then create communicatino channels as needed/desired from the cached istance.

Do you have a need for multiple channel factories (i.e.., are there multiple services)? In my experience, that's where you'll see the biggest benefit in performance. Creating a channel is a fairly inexpensive task; it's setting everything up at the start that takes time.

I would not cache individual channels - I'd create them, use them for an operation, and then close them. If you cache them, they may time out and the channel will fault, then you'll have to abort it and create a new one anyway.

Not sure why you'd want to usea singleton to implement ChannelFactory, especially if you're going to create it and cache it, and there's only one endpoint.

I'll post some example code later when I have a bit more time.

UPDATE: Code Examples

Here is an example of how I implemented this for a project at work. I used ChannelFactory<T>, as the application I was developing is an n-tier app with several services, and more will be added. The goal was to have a simple way to create a client once per life of the application, and then create communication channels as needed. The basics of the idea are not mine (I got it from an article on the web), though I modified the implementation for my needs.

I have a static helper class in my application, and within that class I have a dictionary and a method to create communication channels from the channelf factory.

The dictionary is as follows (object is the value as it will contain different channel factories, one for each service). I put "Cache" in the example as sort of a placeholder - replace the syntax with whatever caching mechanism you're using.

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;
    }
}

Next is a method to create a communication channel from the factory instance. The method checks to see if the factory exists first - if it does not, it creates it, puts it in the dictionary and then generates the channel. Otherwise it simply generates a channel from the cached instance of the factory.

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;
}

I've stripped this example down some from what I use at work. There's a lot you can do in this method - you can handle multiple bindings, assign credentials for authentication, etc. Its pretty much your one stop shopping center for generating a client.

Finally, when I use it in the application, I generally create a channel, do my business, and close it (or abort it if need be). For example:

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);
}

Hopefully the above examples will give you something to go on. I've been using something similar to this for about a year now in a production environment and it's worked very well. 99% of any problems we've encountered have usually been related to something outside the application (either external clients or data sources not under our direct control).

Let me know if anything isn't clear or you have further questions.

Hardboard answered 20/10, 2011 at 20:36 Comment(5)
Thanks Tim. You really did provide some valuable information. I'll definitely be looking out for your example!Wolfgang
@user384080 - the code is in my answer. If that isn't clear, let me know. Thanks.Hardboard
@Hardboard There is a bug in your implementation. You cache factories by contract type no matter what is the address. You should have a key which contain both contract type and address.Puerilism
The exception handling seems lacking. You do not know who threw the exception (could be DoSomething) so why did you decide you need to abort the channel instead of just closing it ?Midships
@Midships - This is stripped down code from what I used at work. At work, more is done, and I included a comment in sample code // I also do logging of some sort here.Hardboard
R
6

You could always just make your ChannelFactory static for each WCF Contract...

You should be aware that from .Net 3.5 the proxy objects are pooled for performance reasons by the channel factory. Calling the ICommunicationObject.Close() method actually returns the object to the pool in the hope it can be reused.

I would look at the profiler if you want to do some optimisation, if you can prevent just one IO call being made in your code it could far outweigh any optimisation you will make with the channel factory. Don't pick an area to optimise, use the profiler to find where you can target an optimisation. If you have an SQL database for instance, you will probably find some low hanging fruit in your queries that will get you orders of magnitude performance increases if these haven't already been optimised.

Radii answered 21/10, 2011 at 6:18 Comment(0)
I
4

Creating the Channel costs the performance so much. actually , WCF already has the cache mechanism for the ChannelFactory if you use the ClientBase in the client instead of the pure ChannelFactory. But the cache will be expired if you make some anditional operations(Please google it for details if you want). For the ErOx's issue i got another solution i think it is better. see below:


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 { }
}

you can customize the logic and the parameters of the Initiate method yourself if you got another requirement. but this initiator class is not limited only one endpoint. it is powerful for all of the endpoint in your application. hopefully. it works well for you. BTW. this solution is not from me. i got this from a book.

Indeed answered 2/4, 2012 at 14:40 Comment(1)
Note that the lock is used incorrectly. The lock should be taken on the call to ContainsKey as well.Encephalic

© 2022 - 2024 — McMap. All rights reserved.