RELATED: "WCF and Client Proxies" and "WCF Service Instancing."
This month we ll explore scenarios for managing the lifetime of WCF proxies in different architectural scenarios, and also cover related performance improvements released with .NET 3.0 SP1.
Calling a WCF service requires clients to construct a proxy which really means constructing a channel factory and, ultimately, a strongly-typed channel for the service endpoint. Building the client s channel stack carries some overhead, which can impact multithreaded applications. This includes Windows Forms or WPF applications that present a user interface, and server-side applications that consume WCF services, such as ASP.NET pages and other WCF services. In this article I ll explore the appropriate optimizations for caching the channel factory or the entire channel, depending on the application architecture. In addition, I ll explain new features released with .NET 3.0 SP1 that now provide some built-in optimizations.
A Quick Note on .NET 3.0 SP1
.NET 3.0 SP1 was released during the same timeframe as Visual Studio 2008 and .NET 3.5 (November 2007). The service pack is actually a prerequisite to .NET 3.5 and, thus, is installed with .NET 3.5. You can also apply the service pack to your .NET 3.0 installations separately. A few assemblies are updated when the service pack is applied.
Here s a direct link to .NET 3.0 SP1 if you are not installing .NET 3.5. This also provides information on the core changes in the service pack under the KB Articles link.
There are several improvements in .NET 3.0 SP1 among them, optimizations related to WCF proxies and lifetime management of the underlying channel factory. This will be the feature that I discuss as part of this article.
Channel Caching Options
Your options for channel caching in any application consist 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 is
generated that implements your service contract and inherits ClientBase
[ServiceContract(Namespace="http://www.thatindigogirl.com/ samples/2006/06")] public interface ICounterServicePerCall { [OperationContract] int IncrementCounter(); } public partial class CounterServicePerCallClient : ClientBase< ICounterServicePerCall>, ICounterServicePerCall {...}
Figure 1: This class implements the service contract ICounterServicePerCall.
If you choose to not cache the proxy reference, your code might 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 manually
achieve the same result using ChannelFactory
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 proxy or channel reference to achieve channel caching scoping the reference so the application can reuse it for multiple calls and possibly multiple threads.
The only way to cache the channel factory separate from
the channel before SP1 was to use ChannelFactory
// scope the channel factory reference for the application's requirements ChannelFactoryfactory = new ChannelFactory (new NetTcpBinding(),new EndpointAddress("net.tcp://localhost:9000/PerCall")) ; // create the channel for each call ICounterServicePerCall 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 to
SP1 the only way to cache the channel factory separate from the channel was to
use ChannelFactory
Of course, you d have to uniquely scope individual channel factory references if there are several (to ensure using the correct factory to create channels for each endpoint with which you communicate).
.NET 3.0 SP1 introduced
a new most recently used (MRU) channel factory cache that is managed
automatically as you use proxies that derive from ClientBase
Once you have a full grasp of the channel caching options available, you must think through what is appropriate for your application. In other words:
- When is caching appropriate?
- What level of caching is appropriate?
To answer these questions, one must always consider the application architecture. I ll provide some guidance on this based on Windows client- and server-side application scenarios in forthcoming sections.
Channel Factory Caching in SP1
The new channel caching feature released with .NET
3.0 SP1 is transparent to developers in the sense that channel factory caching
in the MRU cache is handled automatically by ClientBase
That s the high-level perspective, but there are some important 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
// non-duplex constructors protected ClientBase(); protected ClientBase(string endpointConfigurationName); protected ClientBase(Binding binding, EndpointAddress remoteAddress); protected ClientBase(string endpointConfigurationName, EndpointAddress remoteAddress); protected ClientBase(string endpointConfigurationName, string remoteAddress); // duplex constructors protected 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: Constructors
supplied by ClientBase
Parameters to these constructors are used to either cache
a 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 being
relevant to DuplexClientBase
Aside from constructor choice affecting the use of the MRU
cache, accessing certain properties of the ClientBase
Channel factory references in the MRU cache are considered a 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 the developer, there is very little reason to not leverage it when you can particularly for Windows client applications. However, there are definitely some scenarios where this approach for channel factory caching is not practical or even possible. The next sections will look at some application scenarios and recommended approaches.
Windows Clients and Channel Caching
Windows clients built with Windows Forms or WPF typically create a proxy and cache it for the lifetime of the application. Figure 4 illustrates a client application that communicates with two services, Service A and Service B. Two proxies are constructed when the application begins; each is used for the lifetime of the application to communicate with their respective service endpoints. Because the services use PerCall instancing, each call from the client gets its own service instance, but a single channel factory and channel are constructed at the client to invoke service operations.
Figure 4: Channel caching for the
lifetime of a Windows application.
Figure 4 assumes that you are caching the proxy reference that
includes both the channel factory and channel. This would be equivalent to
using ChannelFactory
If the channel has a transport session (named pipes, TCP, or HTTP with reliable sessions or secure sessions), it is possible for the channel to become faulted, possibly from a timeout or an uncaught exception. In this case, a new channel must be created at the client. With SP1, if the proxy is constructed with parameter values that match a channel factory in the MRU cache (see Figure 5), the overhead of recreating the client channel is reduced.
Figure 5: Leveraging the MRU cache
to construct a new proxy.
If the client application is multithreaded, channel caching is an absolute necessity, particularly if each thread is updating user interface components. If each thread constructs a new proxy to communicate with services, UI updates can become unbearably slow. If there are enough UI threads, even the MRU cache is not sufficient to improve this perception to end users. In this case, channel caching is the best possible solution where all threads share the same proxy or channel reference, as shown in Figure 6. Certainly, if something causes the channel to fault, the presence of the MRU cache is still useful for optimizing channel recreation.
Figure 6: Multiple threads sharing
the same proxy reference.
Remember that in the presence of a transport session, even PerCall services must be set for ConcurrencyMode.Multiple to allow a single proxy to send multiple concurrent requests to the same channel.
There is at least one clear case where a Windows client application will not be able to leverage the MRU caching feature to optimize channel recreation. Because accessing the ClientCredentials property removes the matching MRU cache entry for the proxy, clients that supply credentials collected at run time will never use the MRU cache. For example, the following code sets UserName credentials a typical requirement of Internet-facing WCF services:
CounterServicePerCallClient proxy = new CounterServicePerCallClient("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 are removed from the cache. Fortunately, Windows clients benefit the most from channel caching. The MRU cache, although useful in the case where channel recreation is necessary, will not make a significant difference unless the channel is consistently faulted across multiple client threads that rely on the channel.
To improve channel recreation performance you can create
the channel factory directly using ChannelFactory
ChannelFactoryfactory = new ChannelFactory (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 caching option 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 |
The best possible use case for multithreaded
applications with frequent UI updates. ChannelFactory |
Figure 7: Windows client channel caching options and typical usage.
Server-side Clients and Channel Caching
Server-side clients are a much different animal from Windows applications when it comes to practical options for caching channel factories and channels. Figure 8 illustrates the architecture where multiple concurrent threads from an ASP.NET application or WCF service hosted in the Web server tier call a WCF service hosted in the application server tier.
Figure 8: Server-side clients
consuming a WCF service from multiple concurrent threads.
This scenario is different from the Windows client scenario 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 your practical options for channel caching.
Caching a proxy or channel reference is simply not an option if you want to load balance calls from the Web server tier to the application server tier unless the protocol used is HTTP without sessions (BasicHttpBinding). The typical protocol between Web and application servers is TCP (NetTcpBinding), which reduces this likelihood. If you cache the channel, server affinity is established which can crush your scalability goals for the application as a whole, even if it seems to improve performance when you test with only a few concurrent users.
Caching the channel factory can be a viable option to
improve performance, while still allowing the application to distribute calls
to load-balanced servers because a new channel is created for each thread.
Caching the channel factory in a server-side scenario cannot likely leverage
the MRU cache supplied by ClientBase
Using ChannelFactory
In most cases, server-side clients simply pay the price of constructing a new channel for each call, without any caching benefits. This satisfies the need to scale out and provide the required credentials for each call 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 a multithreaded server application can withstand two to three WCF service calls without caching the channel factory or channel and still achieve performance benchmarks required by the application for the individual call. The overhead of the WCF plumbing is usually shadowed by the overhead of the application functionality. You should always benchmark your applications to establish an acceptable average time to complete each call. Figure 9 summarizes the typical usage 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 |
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 better
understanding of not only the possible channel caching options available to
you, but what is practical to apply in Windows client and server-side client
scenarios. While the MRU cache is a useful new feature, there are still cases
where raw ChannelFactory
Download the samples for this article at http://www.dasblonde.net/downloads/aspprojuly08.zip.
Michele Leroux Bustamante is Chief Architect of IDesign Inc., Microsoft Regional Director for San Diego, and Microsoft MVP for Connected Systems. At IDesign Michele provides training, mentoring, and high-end architecture consulting services focusing on Web services, scalable and secure architecture design for .NET, federated security scenarios, Web services, and interoperability and globalization architecture. She is a member of the International .NET Speakers Association (INETA), a frequent conference presenter, conference chair for SD West, and is frequently published in several major technology journals. Michele also is on the board of directors for IASA (International Association of Software Architects), and a Program Advisor to UCSD Extension. Her latest book is Learning WCF (O Reilly 2007); see her book blog at http://www.thatindigogirl.com. Reach her at mailto:[email protected], or visit http://www.idesign.net and her main blog at http://www.dasblonde.net.
Additional Resources
Wenlong Dong has a fantastic blog entry with additional details 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