WCF Proxies: To Cache or Not to Cache?

Managing Channel Factory and Channel Lifetime for WCF Applications

15 Min Read
ITPro Today logo in a gray background | ITPro Today

RELATED: "WCF and Client Proxies" and "WCF Service Instancing."

This month we ll explore scenarios for managing thelifetime of WCF proxies in different architectural scenarios, and also coverrelated performance improvements released with .NET3.0 SP1.

Calling a WCF service requires clients to construct aproxy which really means constructing a channel factory and, ultimately, astrongly-typed channel for the service endpoint. Building the client s channelstack carries some overhead, which can impact multithreaded applications. Thisincludes Windows Forms or WPF applications that present a user interface, andserver-side applications that consume WCF services, such as ASP.NETpages and other WCF services. In this article I ll explore the appropriateoptimizations for caching the channel factory or the entire channel, dependingon the application architecture. In addition, I ll explain new featuresreleased with .NET 3.0 SP1 that now providesome built-in optimizations.

 

A Quick Note on .NET 3.0 SP1

.NET 3.0 SP1 was releasedduring the same timeframe as Visual Studio 2008 and .NET3.5 (November 2007). The service pack is actually a prerequisite to .NET3.5 and, thus, is installed with .NET 3.5.You can also apply the service pack to your .NET3.0 installations separately. A few assemblies are updated when the servicepack is applied.

Here s a direct link to .NET3.0 SP1 if you are not installing .NET 3.5.This also provides information on the core changes in the service pack underthe KB Articles link.

There are several improvements in .NET3.0 SP1 among them, optimizations related to WCF proxies and lifetimemanagement of the underlying channel factory. This will be the feature that Idiscuss as part of this article.

 

Channel Caching Options

Your options for channel caching in any applicationconsist of the following:

  • No Caching: Create a new channel factory and channel for each call.

  • Channel Factory Caching: Cache the channel factory and use the same factory to create a new channel for each call.

  • Channel Caching: Cache the actual channel returned by the channel factory and use this for each call.

When you generate a proxy using SvcUtil, a class isgenerated that implements your service contract and inherits ClientBase.Consider the class shown in Figure 1 that implements the service contractICounterServicePerCall.

[ServiceContract(Namespace="http://www.thatindigogirl.com/ samples/2006/06")]public interface ICounterServicePerCall{  [OperationContract] int IncrementCounter();}public partial class CounterServicePerCallClient :ClientBase< ICounterServicePerCall>, ICounterServicePerCall {...}

Figure 1: Thisclass implements the service contract ICounterServicePerCall.

If you choose to not cache the proxy reference, your codemight look like this if you create a new proxy for each call:

CounterServicePerCallClient proxy = new CounterServicePerCallClient(new NetTcpBinding(),new EndpointAddress("net.tcp://localhost:9000/PerCall"));proxy.IncrementCounter();proxy.Close();

In .NET 3.0 (before SP1),this would create a new channel factory and channel each time. You can manuallyachieve the same result using ChannelFactory instead of the generatedproxy by calling the static CreateChannel method exposed by ChannelFactoryto create a new channel for each call:

ICounterServicePerCall proxy = ChannelFactory.CreateChannel(new NetTcpBinding(), new EndpointAddress("net.tcp:// localhost:9000/PerCall"));proxy.IncrementCounter();((ICommunicationObject)proxy).Close();

In both cases, you could opt to cache the returned proxyor channel reference to achieve channel caching scoping the reference so theapplication can reuse it for multiple calls and possibly multiple threads.

The only way to cache the channel factory separate fromthe channel before SP1 was to use ChannelFactory to create thefactory first, then explicitly use the cached factory to construct a newchannel for each call (see Figure 2).

// scope the channel factory reference for the application's requirementsChannelFactory factory = new ChannelFactory(new NetTcpBinding(),new EndpointAddress("net.tcp://localhost:9000/PerCall")) ;// create the channel for each callICounterServicePerCall proxy = factory.CreateChannel();proxy.IncrementCounter();  ((ICommunicationObject)proxy).Close();// clean up the factory when the application no longer needs it  ((ICommunicationObject)factory).Close();

Figure 2: Prior toSP1 the only way to cache the channel factory separate from the channel was touse ChannelFactory to create the factory, then explicitly use thecached factory to construct a new channel for each call.

Of course, you d have to uniquely scope individual channelfactory references if there are several (to ensure using the correct factory tocreate channels for each endpoint with which you communicate).

.NET 3.0 SP1 introduceda new most recently used (MRU) channel factory cache that is managedautomatically as you use proxies that derive from ClientBase. Thismakes it possible to cache the channel factory while using a generated proxy,without having to write the code to handle caching efforts. In fact, your codecould construct a new proxy instance for each call without caching thereference and when the proxy is constructed again the appropriate channelfactory for the new proxy instance would be used if a match is present in theMRU cache. I ll explore how this works in the next section, but the result issimilar to what you might do if you manually cached the channel factory andused it to construct new channel instances for each call.

Once you have a full grasp of the channel caching optionsavailable, you must think through what is appropriate for your application. Inother words:

  • When is caching appropriate?

  • What level of caching is appropriate?

To answer these questions, one must always consider theapplication architecture. I ll provide some guidance on this based on Windowsclient- and server-side application scenarios in forthcoming sections.

 

Channel Factory Caching in SP1

The new channel caching feature released with .NET3.0 SP1 is transparent to developers in the sense that channel factory cachingin the MRU cache is handled automatically by ClientBase. You constructyour proxy, which derives from ClientBase in the usual way. Duringconstruction, ClientBase checks to see if a channel factory hasalready been cached for the endpoint you are calling. If so, it will use thatchannel factory to construct the inner channel for the proxy. If not, it willconstruct a new channel factory for that endpoint, cache it for future use,then construct the inner channel for the proxy.

That s the high-level perspective, but there are someimportant details to consider in this process:

  • The MRU cache is a static member and lives in the application domain. If the application domain is recycled, the MRU cache will be cleared and the process of caching begins again.

  • The MRU cache is limited to 32 entries, thus less frequently used channel factories may be removed if the list grows beyond this for a particular application domain.

  • MRU caching behavior varies based on your choice of proxy constructor and other properties set on the proxy prior to opening the channel. It is very important to use the correct constructor if you want to leverage the MRU cache.

  • Entries in the MRU cache are keyed according to specific properties of the proxy as set by the constructor. It is important to understand which properties are used to key channel factories stored in the MRU cache.

There are several constructors supplied by ClientBase(see Figure 3).

// non-duplex constructorsprotected ClientBase();protected ClientBase(string endpointConfigurationName);protected ClientBase(Binding binding, EndpointAddress remoteAddress);protected ClientBase(string endpointConfigurationName, EndpointAddress remoteAddress);protected ClientBase(string endpointConfigurationName, string remoteAddress);// duplex constructorsprotected ClientBase(InstanceContext callbackInstance);protected ClientBase(InstanceContext callbackInstance, string endpointConfigurationName);protected ClientBase(InstanceContext callbackInstance, Binding binding,EndpointAddress remoteAddress);protected ClientBase(InstanceContext callbackInstance, string endpointConfigurationName, EndpointAddress remoteAddress);protected ClientBase(InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress);

Figure 3: Constructorssupplied by ClientBase.

 

Parameters to these constructors are used to either cachea new channel factory or find an existing channel factory reference.Constructor parameters used to cache the channel factory are:endpointConfigurationName, remoteAddress, and callbackInstance (the latter beingrelevant to DuplexClientBase proxies only). Constructors that accept aBinding parameter type cannot be used to cache the channel factory so if youconstruct your bindings programmatically, you can t take advantage of the MRUcache.

Aside from constructor choice affecting the use of the MRUcache, accessing certain properties of the ClientBase reference willalso cause a matching MRU cache entry to be released. To be specific, removalof the matching MRU cache entry takes place if you access the ChannelFactory,ClientCredentials, or Endpoint properties of the proxy reference.

Channel factory references in the MRU cache are considereda unique match to a particular proxy reference based on the following:

  • The name of the configuration setting used to construct the proxy must match. If not provided, this value will be * internally which means the proxy was using the default client endpoint in the configuration section.

  • The same remote address must be used, which means any parameters passed to initialize the EndpointAddress of the proxy, such as the address, listen URI, and other properties of the EndpointAddress type.

  • The same callback reference must be used for duplex proxies. That means passing the same reference, not a duplicate instance of the same callback type.

Because the use of the MRU cache is transparent to thedeveloper, there is very little reason to not leverage it when you can particularlyfor Windows client applications. However, there are definitely some scenarioswhere this approach for channel factory caching is not practical or evenpossible. The next sections will look at some application scenarios andrecommended approaches.

 

Windows Clients and Channel Caching

Windows clients built with Windows Forms or WPF typicallycreate a proxy and cache it for the lifetime of the application. Figure 4illustrates a client application that communicates with two services, Service Aand Service B. Two proxies are constructed when the application begins; each isused for the lifetime of the application to communicate with their respectiveservice endpoints. Because the services use PerCall instancing, each call fromthe client gets its own service instance, but a single channel factory andchannel are constructed at the client to invoke service operations.


Figure 4: Channel caching for thelifetime of a Windows application.

Figure 4 assumes that you are caching the proxy reference thatincludes both the channel factory and channel. This would be equivalent tousing ChannelFactory and caching the channel reference fromCreateChannel.

If the channel has a transport session (named pipes, TCP,or HTTP with reliable sessions or secure sessions), it is possible for thechannel to become faulted, possibly from a timeout or an uncaught exception. Inthis case, a new channel must be created at the client. With SP1, if the proxyis constructed with parameter values that match a channel factory in the MRUcache (see Figure 5), the overhead of recreating the client channel is reduced.


Figure 5: Leveraging the MRU cacheto construct a new proxy.

If the client application is multithreaded, channelcaching is an absolute necessity, particularly if each thread is updating userinterface components. If each thread constructs a new proxy to communicate withservices, UI updates can become unbearably slow. If there are enough UIthreads, even the MRU cache is not sufficient to improve this perception to endusers. In this case, channel caching is the best possible solution where allthreads share the same proxy or channel reference, as shown in Figure 6.Certainly, if something causes the channel to fault, the presence of the MRUcache is still useful for optimizing channel recreation.


Figure 6: Multiple threads sharingthe same proxy reference.

Remember that in the presence of a transport session, evenPerCall services must be set for ConcurrencyMode.Multiple to allow a singleproxy to send multiple concurrent requests to the same channel.

There is at least one clear case where a Windows clientapplication will not be able to leverage the MRU caching feature to optimizechannel recreation. Because accessing the ClientCredentials property removesthe matching MRU cache entry for the proxy, clients that supply credentialscollected at run time will never use the MRU cache. For example, the followingcode sets UserName credentials a typical requirement of Internet-facing WCFservices:

CounterServicePerCallClient proxy = newCounterServicePerCallClient("netTcp2", new   EndpointAddress("net.tcp://localhost:9000/PerCall"));proxy.ClientCredentials.UserName.UserName = "user";proxy.ClientCredentials.UserName.Password = "password";proxy.IncrementCounter();proxy.Close();

As soon as the ClientCredentials property is accessed,previously cached MRU entries for the same configuration name and address areremoved from the cache. Fortunately, Windows clients benefit the most fromchannel caching. The MRU cache, although useful in the case where channelrecreation is necessary, will not make a significant difference unless thechannel is consistently faulted across multiple client threads that rely on thechannel.

To improve channel recreation performance you can createthe channel factory directly using ChannelFactory, set theClientCredentials property, and create your own caching mechanism for thefactory in the event channel recreation is required. The following codeillustrates how to set credentials on the ChannelFactory reference:

ChannelFactory factory = newChannelFactory(new NetTcpBinding(),new EndpointAddress("net.tcp://localhost:9000/PerCall")) ;factory.Credentials.UserName.UserName = "user";factory.Credentials.UserName.Password = "password"; 

Figure 7 summarizes the typical usage of each cachingoption for Windows clients.

Caching Option

Typical Usage

No Caching

Useful for one-off scenarios in a single-threaded client.

Channel Factory Caching using ChannelFactory

Useful to optimize channel recreation when channel factory features such as credentials and binding configurations must be set at run time.

Channel Factory Caching using MRU Cache

Useful to optimize channel recreation when channel factory features are not set at run time. The need to set run time credentials often reduces the value of this option.

Channel Caching using ChannelFactory or ClientBase

The best possible use case for multithreaded applications with frequent UI updates. ChannelFactory should be used to optimize channel factory caching if run-time settings are required.

Figure 7: Windowsclient channel caching options and typical usage.

 

Server-side Clients and Channel Caching

Server-side clients are a much different animal fromWindows applications when it comes to practical options for caching channelfactories and channels. Figure 8 illustrates the architecture where multipleconcurrent threads from an ASP.NETapplication or WCF service hosted in the Web server tier call a WCF servicehosted in the application server tier.


Figure 8: Server-side clientsconsuming a WCF service from multiple concurrent threads.

This scenario is different from the Windows clientscenario in the following ways:

  • There is an expectation that there will always be multiple concurrent threads accessing the same page or service operation. Each thread will require access to a client channel to call downstream services.

  • Calls may originate from different application users, thus each thread may need to pass information about that specific user to downstream services by setting channel credentials or custom application headers.

  • Application servers will likely be load balanced, which means that each thread should not have affinity to a particular server machine.

The characteristics of the server-side scenario limit yourpractical options for channel caching.

Caching a proxy or channel reference is simply not anoption if you want to load balance calls from the Web server tier to the applicationserver tier unless the protocol used is HTTP without sessions(BasicHttpBinding). The typical protocol between Web and application servers isTCP (NetTcpBinding), which reduces thislikelihood. If you cache the channel, server affinity is established whichcan crush your scalability goals for the application as a whole, even if it seemsto improve performance when you test with only a few concurrent users.

Caching the channel factory can be a viable option toimprove performance, while still allowing the application to distribute callsto load-balanced servers because a new channel is created for each thread.Caching the channel factory in a server-side scenario cannot likely leveragethe MRU cache supplied by ClientBase for two reasons: bindingconfigurations are likely to be pulled from a common database entry tofacilitate server-farm deployments, and credentials are likely to be set at runtime on the proxy for each call.

Using ChannelFactory you can still achievechannel factory caching with your own custom MRU cache. This still implies animportant restriction: calls to the same service endpoint that share thechannel factory must also share the same credentials. That means you can t passdifferent credentials for each thread calling application services from the Webserver tier. One scenario where this is not an issue is if you use the samecertificate or Windows credential to authenticate to downstream services. Inthis case, if you need to pass information about the authenticated user, youcan use custom headers rather than a security token.

In most cases, server-side clients simply pay the price ofconstructing a new channel for each call, without any caching benefits. Thissatisfies the need to scale out and provide the required credentials for eachcall using appropriate security tokens instead of the custom header workaround.Performance may be slower if compared side-by-side with each caching approach,but the benefits of horizontal scaling outweigh this concern. In most cases amultithreaded server application can withstand two to three WCF service callswithout caching the channel factory or channel and still achieve performancebenchmarks required by the application for the individual call. The overhead ofthe WCF plumbing is usually shadowed by the overhead of the applicationfunctionality. You should always benchmark your applications to establish anacceptable average time to complete each call. Figure 9 summarizes the typicalusage of each caching option for server-side clients.

Caching Option

Typical Usage

No Caching

Often necessary due to unique credential requirements for each thread.

Channel Factory Caching using ChannelFactory

Useful to optimize channel creation for multiple threads that can share the same channel factory features, such as credentials and binding configurations.

Channel Factory Caching using MRU Cache

Not useful because most server-side scenarios rely on run-time binding configurations and credential settings. Could be useful if cached in the context of a session.

Channel Caching using ChannelFactory or ClientBase

Not useful because most server-side scenarios require calls to be load balanced.

Figure 9:Server-side client channel caching options and typical usage.

 

Conclusion

After reading this article you should have a betterunderstanding of not only the possible channel caching options available toyou, but what is practical to apply in Windows client and server-side clientscenarios. While the MRU cache is a useful new feature, there are still caseswhere raw ChannelFactory caching will be necessary. In addition, don tbe overly concerned if you can t cache your channel factory or channel inserver-side scenarios, because the goal should be overall scalability in thatcase. It is when a user interface is involved, as with Windows clients, thatcaching becomes much more necessary to perceived performance in particularwith multithreaded clients.

Download the samplesfor this article at http://www.dasblonde.net/downloads/aspprojuly08.zip.

 

Michele LerouxBustamante is Chief Architect of IDesign Inc., Microsoft Regional Directorfor San Diego, and Microsoft MVP for Connected Systems. At IDesign Micheleprovides training, mentoring, and high-end architecture consulting servicesfocusing on Web services, scalable and secure architecture design for .NET,federated security scenarios, Web services, and interoperability andglobalization architecture. She is a member of the International .NETSpeakers Association (INETA), a frequent conference presenter, conference chairfor SD West, and is frequently published in several major technology journals.Michele also is on the board of directors for IASA (International Associationof Software Architects), and a Program Advisor to UCSD Extension. Her latestbook is Learning WCF (O Reilly 2007);see her book blog at http://www.thatindigogirl.com.Reach her at mailto:[email protected], orvisit http://www.idesign.net and her mainblog at http://www.dasblonde.net.

 

Additional Resources

Wenlong Dong has a fantastic blog entry with additionaldetails about the MRU cache: http://blogs.msdn.com/wenlong/archive/2007/10/27/performance-improvement-of-wcf-client-proxy-creation-and-best-practices.aspx

Learning WCF (O Reilly,2007): http://www.thatindigogirl.com(get all the book code here!)

Michele s blog: http://www.dasblonde.net

IDesign: http://www.idesign.net

Read more about:

Microsoft
Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like