Foundations

Synchronization Contexts in WCF

Juval Lowy

Code download available at:Foundations2007_11.exe(241 KB)

Contents

What Are .NET Synchronization Contexts?
WCF and Synchronization Contexts
Service Custom Synchronization Context
Thread Affinity Synchronization Context
Priority Processing
Callbacks and Synchronization Context
Why Use Synchronization Contexts?

One of the more useful features of the Windows® Communication Foundation (WCF) is its reliance on the Microsoft® .NET Framework synchronization context to marshal calls to the service instance (or to a callback object). This mechanism provides for both productivity-oriented development and for powerful extensibility. In this column, I describe briefly what synchronization contexts are and how WCF uses them, and then proceed to demonstrate various options for extending WCF to use custom synchronization contexts, both programmatically and declaratively. In addition to seeing the merits of custom synchronization contexts, you will also see some advanced .NET programming as well as WCF extensibility techniques.

What Are .NET Synchronization Contexts?

The .NET Framework 2.0 introduced a little-known feature called the synchronization context, defined by the class SynchronizationContext in the System.Threading namespace:

public delegate void SendOrPostCallback(object state); public class SynchronizationContext { public virtual void Post(SendOrPostCallback callback,object state); public virtual void Send(SendOrPostCallback callback,object state); public static void SetSynchronizationContext (SynchronizationContext context); public static SynchronizationContext Current {get;} //More members }

The synchronization context is stored in the thread local storage (TLS). Every thread created with the .NET Framework 2.0 may have a synchronization context (similar to an ambient transaction) obtained via the static Current property of SynchronizationContext. Current may return null if the current thread has no synchronization context. The synchronization context is used to bounce a method call between a calling thread and a target thread or threads, in case the method cannot execute on the original calling thread. The calling thread wraps the method it wants to marshal to the other thread (or threads) with a delegate of the type SendOrPostCallback, and provides it to the Send or Post methods, for synchronous or asynchronous execution respectively. You can associate a synchronization context with your current thread by calling the static method SetSynchronizationContext.

By far, the most common use of a synchronization context is with UI updates. With all multithreaded Windows technologies, from MFC to Windows Forms to WPF, only the thread that created a window is allowed to update it by processing its messages. This constraint has to do with the underlying use of the Windows message loop and the thread messaging architecture. The messaging architecture creates a problem when developing a multithreaded UI application—you would like to avoid blocking the UI when executing lengthy operations or receiving callbacks. This, of course, necessitates the use of worker threads, yet those threads cannot update the UI directly because they are not the UI thread.

In order to address this problem in the .NET Framework 2.0, the constructor of any Windows Forms-based control or form checks to see if the thread it is running on has a synchronization context, and if it does not have one, the constructor attaches a new synchronization context (called WindowsFormsSynchronizationContext). This dedicated synchronization context converts all calls to its Post or Send methods into Windows messages and posts them to the UI thread message queue to be processed on the correct thread. The Windows Forms synchronization context is the underlying technology behind the commonly used BackgroundWorker helper control.

WCF and Synchronization Contexts

By default, all WCF service calls (and callbacks) execute on threads from the I/O Completion thread pool. That pool has 1,000 threads by default, none of them under the control of your application. Now imagine a service that needs to update some user interface. The service should not access the form, control or window directly because it is on the wrong thread. To deal with that, the ServiceBehaviorAttribute offers the UseSynchronizationContext property, defined as:

[AttributeUsage(AttributeTargets.Class)] public sealed class ServiceBehaviorAttribute : ... { public bool UseSynchronizationContext {get;set;} //More members }

The default value of UseSynchronizationContext is set to true. To determine which synchronization context the service should use, WCF looks at the thread that opened the host. If that thread has a synchronization context and UseSynchronizationContext is set to true, then WCF automatically marshals all calls to the service to that synchronization context. There is no explicit interaction with the synchronization context required of the developer. WCF does that by providing the dispatcher of each endpoint with a reference to the synchronization context, and the dispatcher uses that synchronization context to dispatch all calls.

For example, suppose the service MyService (defined in Figure 1) needs to update some UI on the form MyForm. Because the host is opened after the form is constructed, the opening thread already has a synchronization context, and so all calls to the service will automatically be marshaled to the correct UI thread.

Figure 1 Using the UI Synchronization Context

[ServiceContract] interface IMyContract {...} class MyService : IMyContract { /* some code to update MyForm */ } class MyForm : Form {...} static class Program { static void Main() { Form form = new MyForm();//Sync context established here ServiceHost host = new ServiceHost(typeof(MyService)); host.Open(); Application.Run(form); host.Close(); } }

If you were to simply open the host in Main and use the code generated by the Windows Forms designer, you would not benefit from the synchronization context, since the host is opened without it present:

//No automatic use of synchronization context static void Main() { ServiceHost host = new ServiceHost(typeof(MyService)); host.Open(); Application.Run(new MyForm());//Synchronization context //established here host.Close(); }

Service Custom Synchronization Context

Utilizing a custom synchronization context by your service or resources has two aspects to it: implementing the synchronization context and then installing it. The source code accompanying this column contains my ThreadPoolSynchronizer class, which is defined as:

public class ThreadPoolSynchronizer : SynchronizationContext,IDisposable { public ThreadPoolSynchronizer(uint poolSize); public ThreadPoolSynchronizer(uint poolSize,string poolName); public void Dispose(); public void Close(); public void Abort(); }

ThreadPoolSynchronizer marshals all calls to a custom thread pool, where the calls are first queued up, then multiplexed on the available threads. The size of the pool is provided as a construction parameter. If the pool is maxed out, the call will be pending in the queue until a thread is available. You can also provide a pool name (which will be the prefix of the name of the threads in the pool). Disposing or closing ThreadPoolSynchronizer kills all threads in the pool gracefully, that is, after allowing the engaged threads to complete their task. The Abort method causes a less-than-graceful shutdown, as it terminates all threads abruptly. Implementing ThreadPoolSynchronizer had nothing to do with WCF, so I chose not to include the code in this column. The classic use for a custom thread pool is with a server application that needs to maximize its throughput by controlling the underlying worker threads and their assignments.

To associate your service with the custom thread pool, you can manually attach ThreadPoolSynchronizer to the thread opening the host:

SynchronizationContext syncContext = new ThreadPoolSynchronizer(3); SynchronizationContext.SetSynchronizationContext(syncContext); using(syncContext as IDisposable) { ServiceHost host = new ServiceHost(typeof(MyService)); host.Open(); /* Some blocking operations */ host.Close(); }

The thread pool will have three threads. Using the same service definition as in Figure 1, MyService will have an affinity to those threads. All calls to the service will be channeled to them, regardless of the service concurrency mode or instancing mode, and across all endpoints and contracts supported by the service.

The problem with the above code is that the service is at the mercy of the hosting code. What if, by design, the service is required to execute on the pool? It would be better to apply the thread pool declaratively, as part of the service definition. To that end, I wrote the ThreadPoolBehaviorAttribute:

[AttributeUsage(AttributeTargets.Class)] public class ThreadPoolBehaviorAttribute : Attribute, IContractBehavior, IServiceBehavior { public ThreadPoolBehaviorAttribute(uint poolSize,Type serviceType); public ThreadPoolBehaviorAttribute(uint poolSize,Type serviceType, string poolName); }

You apply the attribute directly on the service, while providing the service type as a constructor parameter:

[ThreadPoolBehavior(3,typeof(MyService))] class MyService : IMyContract {...}

The attribute provides an instance of ThreadPoolSynchronizer to the dispatchers of the service's endpoints. The key in implementing ThreadPoolBehaviorAttribute is knowing how and when to hook up the dispatchers with the synchronization context. To facilitate this, ThreadPoolBehaviorAttribute supports the special WCF extensibility interface IContractBehavior:

public interface IContractBehavior { void ApplyDispatchBehavior(ContractDescription description, ServiceEndpoint endpoint, DispatchRuntime dispatch); // More members }

When a service is decorated with an attribute that supports IContractBehavior, after opening the host (but before forwarding calls to the service) and for each service endpoint, WCF calls the ApplyDispatchBehavior method, providing it with the DispatchRuntime parameter allowing it to affect the dispatcher. Figure 2 lists the implementation of ThreadPoolBehaviorAttribute.

Figure 2 Implementing ThreadPoolBehaviorAttribute

[AttributeUsage(AttributeTargets.Class)] public class ThreadPoolBehaviorAttribute : Attribute,IContractBehavior, IServiceBehavior { //Store values in matching members protected string PoolName {get{...};set{...};} protected uint PoolSize {get{...};set{...};} protected Type ServiceType {get{...};set{...};} public ThreadPoolBehaviorAttribute(uint poolSize,Type serviceType) : this(poolSize,serviceType,null) {} public ThreadPoolBehaviorAttribute(uint poolSize,Type serviceType, string poolName) { PoolName = poolName; ServiceType = serviceType; PoolSize = poolSize; } protected virtual ThreadPoolSynchronizer ProvideSynchronizer() { if(ThreadPoolHelper.HasSynchronizer(ServiceType) == false) return new ThreadPoolSynchronizer(PoolSize,PoolName); else return ThreadPoolHelper.GetSynchronizer(ServiceType); } void IContractBehavior.ApplyDispatchBehavior (ContractDescription description,ServiceEndpoint endpoint, DispatchRuntime dispatch) { PoolName = PoolName ?? "Pool executing endpoints of " + ServiceType; ThreadPoolHelper.ApplyDispatchBehavior(ProvideSynchronizer(), PoolSize,ServiceType,PoolName,dispatchRuntime); } void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase) { serviceHostBase.Closed += delegate { ThreadPoolHelper.CloseThreads(ServiceType); }; } //Rest of the implementation } public static class ThreadPoolHelper { static Dictionary<Type,ThreadPoolSynchronizer> m_Synchronizers = new Dictionary<Type,ThreadPoolSynchronizer>(); [MethodImpl(MethodImplOptions.Synchronized)] internal static bool HasSynchronizer(Type type) { return m_Synchronizers.ContainsKey(type); } [MethodImpl(MethodImplOptions.Synchronized)] internal static ThreadPoolSynchronizer GetSynchronizer(Type type) { return m_Synchronizers[type]; } [MethodImpl(MethodImplOptions.Synchronized)] internal static void ApplyDispatchBehavior( ThreadPoolSynchronizer synchronizer,uint poolSize,Type type, string poolName,DispatchRuntime dispatch) { if(HasSynchronizer(type) == false) { m_Synchronizers[type] = synchronizer; } dispatch.SynchronizationContext = m_Synchronizers[type]; } [MethodImpl(MethodImplOptions.Synchronized)] public static void CloseThreads(Type type) { if(m_Synchronizers.ContainsKey(type)) { m_Synchronizers[type].Dispose(); m_Synchronizers.Remove(type); } } }

It is a best practice to separate the implementation of a WCF custom behavior attribute from the actual behavior. Let the attribute merely decide on the sequence of events and have a helper class provide the actual behavior. Doing so lets you use the behavior separately, for instance by a custom host. This is why ThreadPoolBehaviorAttribute does not do much—it delegates most of its work to a static helper class called ThreadPoolHelper. The ThreadPoolHelper class provides the HasSynchronizer method, which tells if the specified service type already has a synchronization context, and the GetSynchronizer method, which returns the synchronization context associated with the type. ThreadPoolBehaviorAttribute uses these two methods in the virtual ProvideSynchronizer method to ensure that it creates the pool exactly once for the service type. This check is required since ApplyDispatchBehavior can be called multiple times— once per endpoint, in fact. ThreadPoolBehaviorAttribute is also a custom service behavior because it implements IServiceBehavior:

public interface IServiceBehavior { void Validate(ServiceDescription description,ServiceHostBase host); //More members }

The Validate method of IServiceBehavior uses the service host instance to subscribe to the host Closed event, where it asks ThreadPoolHelper to terminate all the threads in the pool.

What ThreadPoolHelper does is associate all dispatchers of all endpoints of that service type with the same instance of ThreadPoolSynchronizer. This ensures that all calls are routed to the same pool. ThreadPoolHelper has to be able to map a service type to a particular ThreadPoolSynchronizer, so it declares a static dictionary called m_Synchronizers that uses service types as keys, and ThreadPoolSynchronizer instances as values.

In ApplyDispatchBehavior, ThreadPoolHelper checks to see if m_Synchronizers already contains the provided service type. If the type is not found, ThreadPoolHelper adds the provided ThreadPoolSynchronizer to m_Synchronizers, associating it with the service type. After that, it simply assigns the ThreadPoolSynchronizer instance to the dispatcher:

dispatch.SynchronizationContext = m_Synchronizers[type];

This single line is all that is required to have WCF use the custom synchronization context from now on. In the CloseThreads method, ThreadPoolHelper looks up from the dictionary the ThreadPoolSynchronizer instance and disposes of it (thus gracefully terminating all the worker threads in the pool). All access to the dictionary is automatically synchronized via the MethodImpl attribute and the MethodImplOptions.Synchronized flag. ThreadPoolHelper also verifies that the provided pool size value does not exceed the max concurrent calls value of the dispatcher throttle (not shown in Figure 2).

Thread Affinity Synchronization Context

A pool size of one will in effect create an affinity between a particular thread and all service calls, regardless of the service concurrency and instancing modes. This is particularly useful if the service is required to create some UI, such as a pop-up window, and periodically show, hide, and update it. Being the party creating the window, the service must enforce the fact it is being called on the same thread. Another use for thread affinity is a service that accesses or creates resources that use the TLS. To formalize such requirements, I create the specialized AffinitySynchronizer like so:

public class AffinitySynchronizer : ThreadPoolSynchronizer { public AffinitySynchronizer() : this("AffinitySynchronizer Worker Thread") {} public AffinitySynchronizer(string threadName): base(1,threadName) {} }

While you can install AffinitySynchronizer as in the earlier referenced code, I also defined the ThreadAffinityBehaviorAttribute in Figure 3. This attribute can be used like this:

Figure 3 ThreadAffinityBehaviorAttribute

[AttributeUsage(AttributeTargets.Class)] public class ThreadAffinityBehaviorAttribute : ThreadPoolBehaviorAttribute { public ThreadAffinityBehaviorAttribute(Type serviceType) : this(serviceType,"Affinity Worker Thread") {} public ThreadAffinityBehaviorAttribute( Type serviceType,string threadName):base(1,serviceType, threadName) {} }

[ThreadAffinityBehavior(typeof(MyService))] class MyService : IMyContract {...}

When relying on thread affinity, all service instances are always thread safe, since only a single thread (and the same thread at that) can access them. Configuring the service with ConcurrencyMode .Multiple amounts to creating single-threaded access. Also, be aware that configuring with ConcurrencyMode.Reentrant does not actually provide for reentrancy, since no reentrancy is possible while the single thread is blocked on the call out. It is best to select the default value of ConcurrencyMode.Single when relying on thread affinity.

Priority Processing

By default, all calls to your WCF service will be processed in the order in which they arrive. This is true both if you use the I/O Completion Ports thread pool or the custom thread pool. Normally, this is exactly what you want. But what if some calls have higher priority and you want to process them as soon as they arrive, rather then handling them in order? Even worse, what if the load on your service is such that the underlying service resources are exhausted? What if the throttle is maxed out? In these cases, your higher priority calls will be queued just as the other calls, waiting for the service or its resources to become available. Synchronization contexts offer an elegant solution to this problem: assign a priority to the calls, and have the synchronization context sort the calls as they arrive, before dispatching them to the thread pool for execution. This is exactly what my PrioritySynchronizer class (defined in Figure 4) does.

Figure 4 PrioritySynchronizer

public enum CallPriority { Low, Normal, High } public class PrioritySynchronizer : ThreadPoolSynchronizer { public PrioritySynchronizer(uint poolSize); public PrioritySynchronizer(uint poolSize,string poolName); public static CallPriority Priority {get;set;} }

PrioritySynchronizer derives from ThreadPoolSynchronizer and adds the sorting I just mentioned. Since the Send and Post methods of SynchronizationContext do not take a priority parameter, the client of PrioritySynchronizer has two ways of passing the priority of the call: the first is via the Priority property, which stores the priority (a value of the enum type CallPriority) in the TLS of the calling thread. The second option is via the message headers, which I will discuss later in this column. If left unspecified, Priority defaults to CallPriority.Normal.

I also provide the matching PriorityCallsBehaviorAttribute shown in Figure 5. Using PriorityCallsBehaviorAttribute is straightforward, as you can see here:

Figure 5 Implementing PriorityCallsBehaviorAttribute

[AttributeUsage(AttributeTargets.Class)] public class PriorityCallsBehaviorAttribute : ThreadPoolBehaviorAttribute { public PriorityCallsBehaviorAttribute(uint poolSize,Type serviceType) : this(poolSize,serviceType,null) {} public PriorityCallsBehaviorAttribute(uint poolSize,Type serviceType, string poolName) : base(poolSize,serviceType,poolName) {} protected override ThreadPoolSynchronizer ProvideSynchronizer() { if(ThreadPoolHelper.HasSynchronizer(ServiceType) == false) return new PrioritySynchronizer(PoolSize,PoolName); else return ThreadPoolHelper.GetSynchronizer(ServiceType); } }

[PriorityCallsBehavior(3,typeof(MyService))] class MyService : IMyContract {...}

PriorityCallsBehaviorAttribute overrides ProvideSynchronizer and provides PrioritySynchronizer instead of ThreadPoolSynchronizer. Because PrioritySynchronizer derives from ThreadPoolSynchronizer, this is transparent as far as ThreadPoolHelper is concerned

The real challenge in implementing and supporting priority processing is providing the call priority from the client to the service, and ultimately to PrioritySynchronizer. Using the Priority property of PrioritySynchronizer is only useful for non-WCF clients that interact directly with the synchronization context; it is of no use for a WCF client, whose thread is never used to access the service. While you could provide the priority as an explicit parameter in every method, I wanted a generic mechanism that could be applied on any contract and service. To that end, you have to pass the priority of the call out-of-band, via the message headers. In my February 2007 Foundations column called "Build a Queued WCF Response Service" (see msdn.microsoft.com/msdnmag/issues/07/02/Foundations), I faced a similar problem—passing the address of the response service out-of-band to the service—and I explain in detail the use of the incoming and outgoing headers. While I could use exactly the same technique here, it would be repetitive work. It is better to therefore extend the technique shown in that column, and augment WCF with general-purpose management of extraneous information from the client to the service, in effect, a generic yet type-safe and application-specific custom context. This is what the GenericContext<T> class provides. Figure 6 shows its implementation with error handling removed.

Figure 6 GenericContext<T> Class

[DataContract] public class GenericContext<T> { [DataMember] public readonly T Value; public GenericContext(T value) { Value = value; } public GenericContext() : this(default(T)) {} public static GenericContext<T> Current { get { OperationContext context = OperationContext.Current; if(context == null) return null; return context.IncomingMessageHeaders.GetHeader<GenericContext<T>> typeof(T).ToString(), typeof(T).Namespace); } set { OperationContext context = OperationContext.Current; MessageHeader<GenericContext<T>> genericHeader = new MessageHeader<GenericContext<T>>(value); context.OutgoingMessageHeaders.Add(genericHeader.GetUntypedHeader( typeof(T).ToString(), typeof(T).Namespace)); } } }

GenericContext<T> completely encapsulates the interaction with the message headers. All a client has to do to pass additional information to the service is set the static Current property inside a new OperationContextScope. For example, to pass the number 4 in the headers, use code as shown in Figure 7. You can further encapsulate GenericContext<T> via type-specific derivation such as IntContext shown in Figure 8.

Figure 8 Integer Context

[DataContract] public class IntContext : GenericContext<int> { public IntContext() {} public IntContext(int number) : base(number) {} public static int Number { get return Current.Value; set Current = new IntContext(value); } }

Figure 7 Using GenericContext<T>

MyContractClient proxy = new MyContractClient(); using(OperationContextScope contextScope = new OperationContextScope(proxy.InnerChannel)) { GenericContext<int>.Current = new GenericContext<int>(4); proxy.MyMethod(); }

Using IntContext, Figure 7 would contain a line such as:

IntContext.Current = 4;

Literally any data contract (or serializable) type can be used for the type parameter in the custom context. On the service side, to read the value out of the custom headers, any downstream party can write:

int number = GenericContext<int>.Current.Value;

or using the type-specific custom context:

int number = IntContext.Number;

This is exactly what PrioritySynchronizer does when looking for the call priority. It expects the clients to provide the priority either in the TLS (via the Priority property) or in the form of a custom context that stores the priority in the message headers, using code similar to what you saw already in Figure 7:

GenericContext<CallPriority>.Current = new GenericContext<CallPriority>(CallPriority.High); proxy.MyMethod();

However, I was not quite happy with such code because it is exposed, it couples the client to the priority mechanism, and it forces the client to set the priority every time before using the proxy. To deal with these issues, I created the HeaderClientBase<T,H> proxy base class shown in Figure 9.

Figure 9 HeaderClientBase<T,H>

public abstract partial class HeaderClientBase<T,H> : ClientBase<T> where T : class { protected H Header; public HeaderClientBase() : this(default(H)) {} public HeaderClientBase(string endpointName) : this(default(H),endpointName) {} public HeaderClientBase(H header) { Header = header; } public HeaderClientBase(H header,string endpointName) : base(endpointName) { Header = header; } /* More constructors */ protected virtual object Invoke(string operation,params object[] args) { using(OperationContextScope contextScope = new OperationContextScope(InnerChannel)) { GenericContext<H>.Current = new GenericContext<H>(Header); Type contract = typeof(T); MethodInfo methodInfo = contract.GetMethod(operation); return methodInfo.Invoke(Channel,args); } } }

Like its base class, HeaderClientBase<T,H> accepts the contract type parameter T, but also the custom header type H. HeaderClientBase<T,H> uses the default value of H or a construction-time provided value to pass for all calls. Lacking a code generator support such as SvcUtil, I resorted to reflection to invoke the actual service operation, while completely encapsulating the interaction with the generic context and the operation context scope. You need to derive from HeaderClientBase<T,H> and provide your desired header type, as in Figure 10, providing an integer.

Figure 10 Using HeaderClientBase<T,H>

[ServiceContract] interface IMyContract { [OperationContract] void MyMethod(string text); } class MyContractClient : HeaderClientBase<IMyContract,int>,IMyContract { public MyContractClient(int number) : base(number) {} public void MyMethod(string text) { Invoke("MyMethod",text); } }

By using the code in Figure 10, you can see that Figure 7 is reduced to merely:

MyContractClient proxy = new MyContractClient(4); proxy.MyMethod();

While you could use the same technique as shown in Figure 10 to pass the call priority, for generality's sake it is better to define a general-purpose priority-enabled proxy, the PriorityClientBase<T>, shown in Figure 11.

Figure 11 Defining PriorityClientBase<T>

public abstract partial class PriorityClientBase<T> : HeaderClientBase<T,CallPriority> where T : class { public PriorityClientBase() : this(PrioritySynchronizer.Priority) {} public PriorityClientBase(string endpointName) : this(PrioritySynchronizer.Priority,endpointName) {} public PriorityClientBase(Binding binding, EndpointAddress remoteAddress) : this(PrioritySynchronizer.Priority,binding,remoteAddress) {} public PriorityClientBase(CallPriority priority) : base(priority) {} public PriorityClientBase(CallPriority priority,string endpointName) : base(priority,endpointName) {} public PriorityClientBase(CallPriority priority,Binding binding, EndpointAddress remoteAddress) : base(priority,binding,remoteAddress) {} /* More constructors */ }

PriorityClientBase<T> hardcodes the use of CallPriority for the type parameter H. Also, PriorityClientBase<T> defaults to reading the priority from the TLS (yielding CallPriority.Normal when not found), so it can be used like any other proxy class. With very minor changes to your existing proxy classes, you can now add priority processing support, as shown in Figure 12.

Figure 12 Priority Processing Support

class MyContractClient : PriorityClientBase<IMyContract>,IMyContract { //Reads priority from TLS public MyContractClient() {} public MyContractClient(CallPriority priority) : base(priority) {} public void MyMethod(string text) { Invoke("MyMethod",text); } } MyContractClient proxy = new MyContractClient(CallPriority.High); proxy.MyMethod();

Callbacks and Synchronization Context

When it comes to synchronization contexts, WCF offers similar support for callback objects with the CallbackBehaviorAttribute applied on the callback type:

[AttributeUsage(AttributeTargets.Class)] public sealed class CallbackBehaviorAttribute : Attribute,... { public bool UseSynchronizationContext {get;set;} //More members }

As with the ServiceBehaviorAttribute, UseSynchronizationContext defaults to true. The interesting aspect of a callback object is when affinity to the synchronization context is locked in. WCF will examine the thread that opens the proxy that establishes the duplex communication. If that thread has a synchronization context, then that context will be used for all callbacks. While you could manually install a custom synchronization context by explicitly setting it before opening the proxy, it is better to do so declaratively using an attribute. To affect the callback endpoint dispatcher, the attribute needs to implement the IEndpointBehavior interface:

public interface IEndpointBehavior { void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime); //More members }

In the ApplyClientBehavior method, the ClientRuntime parameter contains a reference to the endpoint dispatcher with the CallbackDispatchRuntime property:

public sealed class ClientRuntime { public DispatchRuntime CallbackDispatchRuntime {get;} //More members }

The rest is identical to the service-side attribute, as demonstrated by my CallbackThreadPoolBehaviorAttribute in Figure 13.

Figure 13 CallbackThreadPoolBehaviorAttribute

[AttributeUsage(AttributeTargets.Class)] public class CallbackThreadPoolBehaviorAttribute : ThreadPoolBehaviorAttribute,IEndpointBehavior { public CallbackThreadPoolBehaviorAttribute(uint poolSize, Type clientType) : this(poolSize,clientType,null) {} public CallbackThreadPoolBehaviorAttribute(uint poolSize, Type clientType,string poolName) : base(poolSize,clientType,poolName) { AppDomain.CurrentDomain.ProcessExit += delegate { ThreadPoolHelper.CloseThreads(ServiceType); }; } void IEndpointBehavior.ApplyClientBehavior (ServiceEndpoint serviceEndpoint,ClientRuntime clientRuntime) { IContractBehavior contractBehavior = this; contractBehavior.ApplyDispatchBehavior(null,serviceEndpoint, clientRuntime.CallbackDispatchRuntime); } //Rest of the implementation }

In fact, in the callback attribute I wanted to reuse as much of the service attribute as possible. To that end, CallbackThreadPoolBehaviorAttribute derives from ThreadPoolBehaviorAttribute. Its constructors pass the client type to the base constructors as the service type. The CallbackThreadPoolBehaviorAttribute implementation of ApplyClientBehavior queries its base class for IContractBehavior (this is how a subclass uses an explicit private interface implementation of its base class) and delegates the implementation to ApplyDispatchBehavior.

The big difference between a client callback attribute and a service attribute is that the callback scenario has no host object to subscribe to its Closed event. To compensate, CallbackThreadPoolBehaviorAttribute monitors the process exit event to close all the threads in the pool. If the client wants to expedite closing those threads, the client can use ThreadPoolBehavior.CloseThreads, as shown in Figure 14.

Figure 14 Using CallbackThreadPoolBehaviorAttribute

[ServiceContract(CallbackContract = typeof(IMyContractCallback))] interface IMyContract { [OperationContract] void MyMethod(); } interface IMyContractCallback { [OperationContract] void OnCallback(); } [CallbackThreadPoolBehavior(3,typeof(MyClient))] class MyClient : IMyContractCallback,IDisposable { MyContractClient m_Proxy; public void CallService() { m_Proxy = new MyContractClient(new InstanceContext(this)); m_Proxy.MyMethod(); } //Called by threads from the custom pool public void OnCallback() {...} public void Dispose() { m_Proxy.Close(); ThreadPoolHelper.CloseThreads(typeof(MyClient)); } }

Just like the service side, to have all the callbacks execute on the same thread (perhaps to create some UI on the callback side), you can configure the callbacks to have a pool size of 1. Better yet, you can define a dedicated callback attribute for such same-thread affinity, as in Figure 15.

Figure 15 CallbackThreadAffinityBehaviorAttribute

[AttributeUsage(AttributeTargets.Class)] public class CallbackThreadAffinityBehaviorAttribute : CallbackThreadPoolBehaviorAttribute { public CallbackThreadAffinityBehaviorAttribute(Type clientType) : this(clientType,"Callback Worker Thread") {} public CallbackThreadAffinityBehaviorAttribute(Type clientType, string threadName) : base(1,clientType,threadName) {} }

Why Use Synchronization Contexts?

Synchronization contexts in the .NET Framework 2.0 are a powerful construct, and coupled with WCF, they yield a superior and productive programming model. The extensibility inherent to WCF makes this combination especially powerful, accomplishing difficult tasks such as thread affinity and priority processing with relatively few lines of code, and all outside the scope of your service. The custom headers and generic context techniques I demonstrated in this column are useful across the landscape of WCF programming. Wherever out-of-band parameters are called for, they provide an elegant, concise and type-safe solution. I also wanted to share with you my thought processes and the trade-offs to consider when evolving your own custom WCF extensions.

Send your questions and comments to mmnet30@microsoft.com.

Juval Lowy is a Software Architect with IDesign providing WCF training and WCF architecture consulting. His recent book is Programming WCF Services (O'Reilly, 2007). He is also the Microsoft Regional Director for the Silicon Valley. Contact Juval at www.idesign.net.