Click here to Skip to main content
Click here to Skip to main content
Go to top

Understanding SynchronizationContext: Part III

, 29 Dec 2008
Rate this:
Please Sign up or sign in to vote.
Using SynchronizationContext with WCF.

Introduction

This article is the last part of the three part series on SynchronizationContext. SynchronizationContext is a class introduced by .NET 2.0 with little documentation or explanation of how to use it. I have tried to explain in part one how to use this class, and in part two, how to create your own SynchronizationContext. In part two, I showed how to build a SynchronizationContext that will marshal code from any .NET thread into a STA thread. I have done this so I can execute COM code that needs to run on the STA thread. The next step is to create a WCF service that will execute all its service operations on the STA thread (using the SynchronizationContext I provided in part two). In this article, I will show you how to configure WCF to provide a custom STA SynchronizationContext so each method on a WCF service will execute on the same STA thread. This will allow me to provide a simple programming model that will be fully compatible with COM, and I will not need to worry about thread safety because all my code will run within the same thread.

WCF - The Power

I will not even try to give a full explanation of WCF here. WCF is vast in functionality, and does much more than just "Remoting" or web services. I am starting to believe that WCF should be treated as a runtime rather than a communication framework. WCF provides us the following programming models "out of the box":

  • built-in support for error handling
  • built-in support for concurrency
  • built-in support for security
  • built-in support for transactions
  • built-in support for data encryption
  • built-in support for durable services
  • built-in support for method interception and inspection (AOP)
  • built-in support for one way communication, and callbacks
  • and much more...

Just listen to this ARCast, where Juval Lowy believes every class should be a WCF class: Every Class a WCF Service, with Juval Lowy. At first, I said to myself, this is too much, every class to be a WCF class is just insane. But, the more I looked at WCF and what it had to offer, the more it made sense to me. WCF is a very extensible framework, allowing the developer to do a lot of custom configuration. For example, providing a SynchronizationContext for your service is something you will not be able to do within native .NET classes, but only in WCF (and that's just one feature among many). Although, I must say I did not go as far as making each class a WCF class. I have decided to make each component a WCF service. Even if I don't plan to run my component remotely, I still believe the benefits of the WCF runtime are worth coding my components as WCF services. Bottom line, WCF is more than web services or Remoting services, it provides a solid programming model that applies in every type of development. If you don't know WCF, I strongly recommend you learn it, it is by far one of the better frameworks Microsoft has released. For this article, I assume you have a basic knowledge of WCF services.

A Simple WCF Service

Let me just say this right away, I have coded a WCF service for this article just for testing. I do not recommend you code services the way that I have. To be more specific, in my code, the service implementation and the service contact are within the same assembly - this is not recommended. However, because I am dealing with testing my SynchronizationContext within a prototype/testing project, I wanted to keep the number of assemblies and code to a minimum. Normally, when I code a WCF service, I have three projects:

  • Project containing the service contact (interface) and possibly any data contacts (DTOs)
  • Project containing the service implementation
  • Project hosting the service (optional)

So please, no bashing, I am stating it here that the code is just for testing and not for production. Now that we got that out of the way, let me show the service contact and the implementation.

[ServiceContract]
public interface IStaService
{

  [OperationContract]
  string DoWorkOnSTAThread(string state);
}

There is really not much to say about this service, it is very simple. Let me show you the implementation of this service:

public class StaService : IStaService
{
  public string DoWorkOnSTAThread(string state)
  {
     ApartmentState aptState = Thread.CurrentThread.GetApartmentState();
     if (aptState == ApartmentState.STA)
        Trace.WriteLine("Using STA thread");

     int id = Thread.CurrentThread.ManagedThreadId;
     Trace.WriteLine("WCF current thread: " + id);
     return "processed by " + aptState.ToString() + " Thread id: " + id.ToString();
  }
}

Notes:

  • DoWorkOnSTAThread will be the method I plan to execute on the STA thread, so I coded some tracking code to make sure I am running this method on the STA thread. I check the ApartmentState and make sure it is an ApartmentState.STA.
  • I also log the thread ID; considering I want all my code to run on the same STA thread, the ID should always be the same.
  • However, I did not write any code to indicate using the STA Sync Context, so for now, STA thread marshaling is not used.

Testing our Service

We will be running our service a few times, so let's learn how to test it. My service is hosted using webdev, and it runs as a web service. To test it, I have used the WCF test client applicaiton. It can normally be found at "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\WcfTestClient.exe". By calling DoWorkOnSTAThread multiple times, I get the following output:

WCFClient.jpg

WCF current thread: 12
WCF current thread: 12
WCF current thread: 11
WCF current thread: 11
WCF current thread: 11
WCF current thread: 13
WCF current thread: 10
WCF current thread: 11
WCF current thread: 10
WCF current thread: 10
WCF current thread: 13

To find out the service behavior, you can view the ServiceDescription within a custom ServiceHost (I will show the custom service host code later in the article).

WCFServiceDescription.jpg

Notice that I am running the same method on multiple threads. This is because, by default, WCF created my service using a PerSession InstanceContextMode and a ConcurrencyMode of Single (see image above). This means that the method will be executed one at a time, but each time, it is executed by a thread assigned by the thread pool. First, you should avoid using PerSession for your services. PerSession simply does not scale, I consider it evil for coding scalable services, so, I am going to change the service behavior.

[ServiceBehavior(UseSynchronizationContext=true, 
 ConcurrencyMode=ConcurrencyMode.Multiple,
 InstanceContextMode=InstanceContextMode.PerCall)]
public class StaService : IStaService
{
  public string DoWorkOnSTAThread(string state)
  {
     ApartmentState aptState = Thread.CurrentThread.GetApartmentState();
     if (aptState == ApartmentState.STA)
        Trace.WriteLine("Using STA thread");

     int id = Thread.CurrentThread.ManagedThreadId;
     Trace.WriteLine("WCF current thread: " + id);
     //throw new Exception("boom");
     return "processed by " + aptState.ToString() + " Thread id: " + id.ToString();
  }
}

Using the WCF service behavior attribute [ServiceBehavior(UseSynchronizationContext=true, ConcurrencyMode=ConcurrencyMode.Multiple, InstanceContextMode=InstanceContextMode.PerCall)], I ask WCF to create this service using InstanceContextMode.PerCall. This means, every method call will create an instance of the service, and as soon as the method completes, the instance is destroyed. Using ConcurrencyMode.Multiple allows for clients to execute methods within this service concurrently, allowing multiple threads to execute the same method at the same time. UseSynchronizationContext=true means that I ask the service to use the SynchronizationContext attached to the host's thread. Using this new service behavior, let's see our output by executing the method three times:

WCF current thread: 12
WCF current thread: 9
WCF current thread: 6

Same results more or less... Our service can scale better, but still have no control on which thread the code is executed on.

Providing your Service a SynchronizationContext

You noticed that irrespective of whether the service is using single or multiple concurrency mode, in both, the invocation is controlled by WCF using different threads from the thread pool. In order to provide your own SynchronizationContext, you have two choices:

  • Set the SynchronizationContext on the hosting thread before opening the host
  • Create your own ServiceBehavior and override the SynchronizationContext on the service endpoint

I will explore the second option simply because we don't always create our own hosting program, and in many cases, the hosting is done in WAS or IIS. Let me show you the ServiceBehvior attribute I created to use STA thread synchronization.

public class StaServiceBehaviorAttribute : Attribute, IContractBehavior, IServiceBehavior
{
  StaSynchronizationContext mStaContext;
  public StaServiceBehaviorAttribute()
  {
     mStaContext = new StaSynchronizationContext();
  }
  #region IContractBehavior Members

  void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, 
    ServiceEndpoint endpoint, 
    System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
  {

  }

  void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription,
    ServiceEndpoint endpoint, 
    System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
  {

  }

  void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription,
    ServiceEndpoint endpoint,
    System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
  {
     dispatchRuntime.SynchronizationContext = mStaContext;
  }

  void IContractBehavior.Validate(ContractDescription contractDescription, 
    ServiceEndpoint endpoint)
  {

  }

  #endregion

  #region IServiceBehavior Members

  void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription,
    System.ServiceModel.ServiceHostBase serviceHostBase,
    System.Collections.ObjectModel.Collection<serviceendpoint /> endpoints,
    System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
  {

  }

  void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, 
    System.ServiceModel.
    ServiceHostBase serviceHostBase)
  {

  }

  void IServiceBehavior.Validate(ServiceDescription serviceDescription, 
    System.ServiceModel.ServiceHostBase serviceHostBase)
  {
     serviceHostBase.Closed += delegate
     {
        mStaContext.Dispose();
     };
  }

  #endregion
}

By creating my own custom ServiceBehvior attribute, I can set certain settings that are otherwise not available to me.

Notice that I am creating the StaSynchronizationContext at the constructor:

public StaServiceBehaviorAttribute()
{
    mStaContext = new StaSynchronizationContext();
}

The other most important part is implementing the ApplydispatchBehvior method. This method will be executed once on each endpoint within your service. It allows me to override the default SynchronizationContext with my own custom one.

void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription,
    ServiceEndpoint endpoint,
    System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
{
    dispatchRuntime.SynchronizationContext = mStaContext;
}

To make sure my STA thread ends correctly, I have also modified the IServiceBehavior.Validate and provided an event handler for closing the host.

void IServiceBehavior.Validate(ServiceDescription serviceDescription,
     System.ServiceModel.ServiceHostBase serviceHostBase)
{
 serviceHostBase.Closed += delegate
 {
    mStaContext.Dispose();
 };
}

Using the STA Synchronization Context

Let's take another look at our service, now that we have created our own Service Behavior to apply a custom Synchronization Context.

[StaServiceBehaviorAttribute]
[ServiceBehavior(UseSynchronizationContext=true, ConcurrencyMode=ConcurrencyMode.Multiple,
 InstanceContextMode=InstanceContextMode.PerCall)]
public class StaService : IStaService
{
  public string DoWorkOnSTAThread(string state)
  {
     ApartmentState aptState = Thread.CurrentThread.GetApartmentState();
     if (aptState == ApartmentState.STA)
        Trace.WriteLine("Using STA thread");

     int id = Thread.CurrentThread.ManagedThreadId;
     Trace.WriteLine("WCF current thread: " + id);
     //throw new Exception("boom");
     return "processed by " + aptState.ToString() + " Thread id: " + id.ToString();
  }
}
  • Notice that I kept the WCF service behavior to use PerCall and ConcurrencyMode.Multiple. Considering we are planning to marshal our code to an STA thread, using ConcurrencyMode.Single will have the same behavior as ConcurrencyMode.Multiple.
  • Notice, I have placed my StaServiceBehaiorAttribue on the service. This will create the STA thread, and apply an STA synchronization context on all the endpoints my service exposes (in the examplen I only have one endpoint).

Very, well, let's try it. But using the WCF client tool, I have send multiple requests to my service, and here are the results:

Using STA thread
WCF current thread: 9
Using STA thread
WCF current thread: 9
Using STA thread
WCF current thread: 9
Using STA thread
WCF current thread: 9
Using STA thread
WCF current thread: 9
Using STA thread
WCF current thread: 9
Using STA thread
WCF current thread: 9
Using STA thread
WCF current thread: 9
  • Notice that all the calls to the WCF method are executed on thread 9
  • Notice that thread 9 is an STA thread as we expected

Now, I am able to control on which thread the WCF service is executing on.

A Word About Hosting

Notice that using this method, you can control the synchronization context of your service no matter what hosting method you choose. If you are hosting using a console application, you could set the synchronization context to STA (using the SynchronizationContext.SetSynchronizationContext). Before opening the host, it will have the same effect. I tried to use a custom ServiceHostFactory and set the synchronization context there, but it did not work. Let me show you the custom ServiceHostFactory and the custom ServiceHost.

public class StaCustomHostFactory : ServiceHostFactory
{
  protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
  {
     return new StaCustomHost(serviceType, baseAddresses);
  }
}
public class StaCustomHost : ServiceHost
{
  public StaCustomHost(Type serviceType, params Uri[] baseAddresses) :
     base(serviceType, baseAddresses)
  {

  }

  protected override void InitializeRuntime()
  {
     int id = Thread.CurrentThread.ManagedThreadId;
     base.InitializeRuntime();
  }

  protected override void ApplyConfiguration()
  {
     int id = Thread.CurrentThread.ManagedThreadId;
     // get the configuration from the app.config first
     base.ApplyConfiguration();

     // create the STA Thread Sync object and attach it to this hosting
     // thread.
     StaSynchronizationContext staContext = new StaSynchronizationContext();
     SynchronizationContext.SetSynchronizationContext(staContext);
  }
}
<%@ ServiceHost 
    Language="C#" Debug="true" 
    Factory="StaServiceHost.StaCustomHostFactory" 
    Service="TestStaService.StaService" 
    CodeBehind="StaService.svc.cs" %>

Notice, I set the SynchronizationContext within the ApplyConfiguration method. WCF will take whatever SynchronizationContext is set on the host's thread and use it for each invocation. However, it did not work at all. I don't know exactly why, but the only explanation I can come up with is that the code within ApplyConfiguration does not run within the same thread as ServiceHost.Open. Considering that the host is opened by webdev or IIS, I really don't know how to set the synchronization context on these threads. Therefore, I recommend you stick to using custom service behavior as I have shown before for overriding the synchronization context of your service's endpoints.

One little comment about closing the host. As you can see in part II, the STA thread is created as soon as the STA SynchronizationContext object is created. In this case, it is created within the service behavior. It is important that this thread ends when the host closes. That's the reason I have coded this event handler:

void IServiceBehavior.Validate(ServiceDescription serviceDescription,
     System.ServiceModel.ServiceHostBase serviceHostBase)
{
 serviceHostBase.Closed += delegate
 {
    mStaContext.Dispose();
 };
}

I have tested this code by stopping the webdev process and placing a break-point on the event handler. I validated that the STA thread is exiting when webdev is closing. Thanks for the small WCF miracles.

Is WCF using Send or Post?

You might be wondering if the Send or the Post of our sync-context is used. I really didn't know, so I set a breakpoint on both the Send and Post methods, and found out that WCF always uses the Post method. I have changed the service concurrency-mode to ConcurrencyMode.Single and still Post was used. So, when using Single or Multiple concurrency, in both cases, the Post method is used to marshal code into the STA thread. What about exceptions? Notice, I have modified my service to throw an exception:

[StaServiceBehaviorAttribute]
[ServiceBehavior(UseSynchronizationContext=true, 
 ConcurrencyMode=ConcurrencyMode.Multiple,
 InstanceContextMode=InstanceContextMode.PerCall)]
public class StaService : IStaService
{
  public string DoWorkOnSTAThread(string state)
  {
     ApartmentState aptState = Thread.CurrentThread.GetApartmentState();
     if (aptState == ApartmentState.STA)
        Trace.WriteLine("Using STA thread");

     int id = Thread.CurrentThread.ManagedThreadId;
     Trace.WriteLine("WCF current thread: " + id);
     throw new Exception("boom");
     //return "processed by " + aptState.ToString() + 
     //       " Thread id: " + id.ToString();
  }
}

Initially, I believed that this might cause the STA thread to terminate. It is an unhandled exception running on the STA thread, but WCF does handle the exception for you. So, the STA thread does not end even if you throw exceptions within the STA thread. WCF catches any unhandled exceptions, and converts them to FaultException on the client thread. This saves our STA thread from ending, so it keeps running regardless if exceptions are thrown. Thank God for another WCF miracle.

Conclusion

In this article, I got into the inner workings of WCF, and grabbed control on "where" a service method is executed on. I am able to "tell" WCF to marshal all the method calls on an STA thread, allowing us not to worry about calling COM objects that are designed to work on the STA thread. This will also allow you to pop a UI within your service method, but I really don't recommend it. By marshaling the code yourself, you can add additional logic to log and validate each invocation. You might even add security at this level, and refuse marshaling a call if it does not fit a certain criteria. However, do not use this as a wild card; service side method interception can also do the same thing. Still, now that you know about this feature, you might find a good use for it within your projects.

Thank you for reading, and happy .NETting.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

mikeperetz
Web Developer
Canada Canada
I am currently working as a team leader with a group of amazing .NET programmers. I love coding with .NET, and I love to apply design patterns into my work. Lately I had some free time, so I decided to write some articles, hoping I will spare someone frustration and anxiety.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberMember 391656811-Jan-12 0:48 
QuestionService not running on main thread [modified] Pinmembervenex119-Oct-11 2:49 
GeneralQuestion for you PinmemberTV Mogul10-Feb-11 9:02 
GeneralRe: Question for you Pinmembermikeperetz20-Feb-11 1:42 
GeneralRe: Question for you PinmemberChris Mankowski18-Apr-11 16:59 
I'd love to read more about this architecture regarding WCF and COMET. Please feel free to share details EnduroUSATour at Y!
GeneralGetting at STA for Winforms is MUCH easier than all this. PinmemberJay R. Wren6-Oct-09 8:02 
GeneralRe: Getting at STA for Winforms is MUCH easier than all this. Pinmvpmikeperetz28-Jun-10 14:13 
GeneralcontRact Pinmembergxdata5-Jan-09 19:00 
GeneralRe: contRact Pinmembermikeperetz6-Jan-09 2:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 29 Dec 2008
Article Copyright 2008 by mikeperetz
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid