Export (0) Print
Expand All

Writing Smart Clients by Using Windows Communication Foundation

Juval Lowy

IDesign, Inc.

February 2008

Summary: There are a number of important design constraints that you must accommodate in your application when combining Microsoft Windows Communication Foundation (WCF) and user-interface (UI) applications. This article explains and illustrates these constraints, and shares best practices and design guidelines. (26 printed pages)

Introduction

WCF Concurrency Management

Synchronization Context

Service Synchronization Context

Callbacks and Client Safety

Asynchronous Calls

Introduction

Your smart-client application—be it Windows Forms or Windows Presentation Foundation (WPF)—is very likely to take advantage of Windows Communication Foundation (WCF). Your front-end clients can use WCF to call services to perform their work. Your client application can call those services synchronously or asynchronously, and even receive callbacks from the services. In addition, your service might need to update the user interface (UI) of some host-side application.

As it turns out, there are a number of important design constraints that you must accommodate in your application when combining WCF and UI applications. After a brief introduction to synchronization in WCF, this article explains and illustrates these constraints. The article ends by discussing occasionally connected clients. You will also see some interesting Microsoft .NET Framework 3.5 programming techniques. Throughout, the article shares best practices and design guidelines.

Incoming client calls are dispatched to a WCF service on threads from the I/O completion thread pool. Because multiple clients can make multiple concurrent calls, the service itself can sustain those calls on multiple threads. If those calls are dispatched to the same instance, you must provide thread-safe access to the service’s in-memory state or risk state corruption and errors. The same is true for the client’s in-memory state during callbacks, because callbacks too are dispatched on threads from the thread pool. In addition to synchronizing access to the instance state, when applicable, all of the services must synchronize access to resources such as static variables or UI controls. Another dimension altogether for concurrency management is ensuring, if required, that the service (or the resources that it accesses) and clients during callbacks get executed on particular threads.

Concurrent access to the service instance is governed by the ConcurrencyMode property of the ServiceBehavior attribute, as shown in the following code snippet:

public enum ConcurrencyMode
{
   Single,
   Reentrant,
   Multiple
}
[AttributeUsage(AttributeTargets.Class)]
public sealed class ServiceBehaviorAttribute : ...
{
   public ConcurrencyMode ConcurrencyMode
   {get;set;}
   //More members
}

The value of the ConcurrencyMode enum controls if and when concurrent calls are allowed on the service instance.

ConcurrencyMode.Single

When the service is set with ConcurrencyMode.Single, WCF will provide automatic synchronization to the service instance and disallow concurrent calls by associating the service instance (actually, the context in which the instance resides) with a synchronization lock. Every call coming into the instance must first try to acquire the lock. If the lock is un-owned, the caller will lock the lock and be allowed in. As soon as the operation returns, WCF will unlock the lock and thus allow in another caller. The important thing is that only one caller at a time is ever allowed. If there are multiple concurrent callers while the lock is locked, the callers are all placed in a queue, and then are served out of the queue in order. If the call times out while blocked, WCF will remove the caller from the queue, and the client will get a TimeoutException. ConcurrencyMode.Single is the WCF default setting.

ConcurrencyMode.Multiple

When the service is set with ConcurrencyMode.Mutiple, WCF will stay out of the way and not synchronize access to the service instance in any way. ConcurrencyMode.Mutiple just means that the service instance is not associated with any synchronization lock, so that concurrent calls are allowed on the service instance as soon as they arrive.

ConcurrencyMode.Reentrant

The ConcurrencyMode.Reentrant value is a refinement of the ConcurrencyMode.Single value. Similarly to ConcurrencyMode.Single, ConcurrencyMode.Reentrant associates the service instance with a synchronization lock, so that concurrent calls on the same instance are never allowed. However, if the reentrant service calls out to another service or a callback, WCF silently releases the synchronization lock that is associated with the instance. This is called reentrant because, if that call out somehow winds its way back to the service instance, it is allowed to reenter the service instance.

Callbacks and Reentrancy

Callbacks are the main reason that reentrancy is available. If a WCF service wants to invoke a duplex callback to its calling client, the service requires reentrancy (or no synchronization at all via ConcurrencyMode.Multiple). The reason is that processing the reply message from the client after the callback returns requires ownership of the instance lock, and so a deadlock would occur if a service with ConcurrencyMode.Single were allowed to call back to its clients.

To allow callbacks, the service must be configured with either ConcurrencyMode.Multiple or (preferably) ConcurrencyMode.Reentrant. Note that the service can still invoke callbacks to other clients or call other services; it is the callback to the calling client that is disallowed. If a service that is configured with ConcurrencyMode.Single tries to invoke a duplex callback, WCF will throw an InvalidOperationException. The only case in which a service that is configured with ConcurrencyMode.Single can call back to its clients is one in which the callback contract operation is configured as one-way, because there will not be any reply message to contend for the lock.

Incoming service calls execute on arbitrary worker threads. Even if the service allows only synchronized access by one thread at a time using ConcurrencyMode.Single, that thread is managed by WCF and is unrelated to any service or resource threads. This means that, by default, the service cannot rely on any kind of thread affinity, which is always being accessed by the same thread. In much the same way, the service cannot rely by default on executing on some host-side custom threads that are created by the host or service developers. The problem with this situation is that some resources might rely on thread affinity; for example, UI resources that are updated by the service must execute and be accessed only by the UI thread.

Whenever an affinity to a particular thread or threads is expected, the service cannot simply execute the call on the incoming WCF worker thread. Instead, the service must marshal the call to the correct thread(s) required by the resource that it accesses.

.NET Framework 2.0 Synchronization Contexts

.NET Framework 2.0 introduced the concept of a synchronization context. The idea is that any party can provide an execution context and have other parties marshal the calls to that context. The synchronization context can be a single thread or any number or designated threads, although typically it will be just a single yet particular thread. All that the synchronization context does is assure that the call executes on the correct thread or threads.

The SynchronizationContext Class

The SynchronizationContext class from the System.Threading namespace represents a synchronization context, as the following code snippet shows:

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 SynchronizationContext Current
   {get;}
   //More members
}

Every thread in .NET Framework 2.0 can have a synchronization context associated with it. You can obtain a thread’s synchronization context by accessing the static Current property of SynchronizationContext. If the thread does not have a synchronization context, Current will return null.

Working with the Synchronization Context

To represent the call to invoke in the synchronization context, you wrap a method with a delegate of the type SendOrPostCallback.

There are two ways to marshal a call to the synchronization context: synchronously and asynchronously. The Send() method will block the caller until the call has completed on the other synchronization context, while the Post() method will merely dispatch it to the synchronization context and then return control to its caller.

For example, to marshal a call synchronously to a particular synchronization context, first you somehow obtain a reference to that synchronization context, and then you use the Send() method, as the following code snippet shows:

//Obtain synchronization context
SynchronizationContext context = ...

SendOrPostCallback doWork = (arg)=>
                            {
                               //The code here guaranteed to
                               //execute on correct thread(s)
                            };
context.Send(doWork,"Some argument");

UI Synchronization Context

The canonical case for utilizing synchronization contexts is with Windows UI frameworks such as Windows Forms or the Windows Presentation Foundation (WPF). For simplicity’s sake, the rest of the discussion in this article will refer only to Windows Forms, although it equally applies to WPF. A Windows UI application relies on the underlying Windows messages and a message-processing loop to process them. The message loop must have thread affinity, because messages to a window are delivered only to the thread that created it.

In general, you must always marshal to the UI thread any attempt to access a Windows control or form, or risk errors and failures. This becomes an issue if your services must update some user interface, as a result of client calls or some other event. Fortunately, Windows Forms support the synchronization-context pattern. The thread that pumps messages has a synchronization context. That synchronization context is the WindowsFormsSynchronizationContext class, as the following code snippet shows:

public sealed class WindowsFormsSynchronizationContext : SynchronizationContext,...
{...}

Whenever you construct a new Control-derived class, such as a form, the constructor of Control installs WindowsFormsSynchronizationContext as the current thread’s synchronization context.

What WindowsFormsSynchronizationContext does is convert the call to Send() or Post() to a custom Windows message, and then post that Windows message to the UI thread’s message queue. Every Windows Forms UI class that derives from Control has a special method that handles this custom message by invoking the supplied SendOrPostCallback delegate. At some point, the custom Windows message is processed by the UI thread, and the delegate is invoked.

Because the window or control can also be called already in the correct synchronization context, to avoid a deadlock when calling Send(), the implementation of the Windows Forms synchronization context verifies that marshaling the call is indeed required. If marshaling is not required, it uses direct invocation on the calling thread.

UI Access and Updates

When a service must update some user interface, it must have some proprietary mechanisms to find the window to update in the first place. As soon as the service has the correct window, it must get hold of that window’s synchronization context and marshal the call to it. Listing 1 shows such a possible interaction:

partial class MyForm : Form
{
   Label m_CounterLabel;
   SynchronizationContext m_SynchronizationContext;

   public MyForm()
   {
      InitializeComponent();
      m_SynchronizationContext = SynchronizationContext.Current;
      Debug.Assert(m_SynchronizationContext != null);
   }
   public SynchronizationContext MySynchronizationContext
   {
      get 
      { 
         return m_SynchronizationContext; 
      }
   }
   public int Counter
   {
      get
      {
         return Convert.ToInt32(m_CounterLabel.Text);
      }
      set
      {
         m_CounterLabel.Text = value.ToString();
      }
   }
}
[ServiceContract]
interface IFormManager
{
   [OperationContract]
   void IncrementLabel();
}
class MyService : IFormManager
{
   public void IncrementLabel()
   {
      MyForm form = Application.OpenForms[0] as MyForm;
      Debug.Assert(form != null);

      SendOrPostCallback callback = delegate
                                    {
                                       form.Counter++;
                                    };
      form.MySynchronizationContext.Send(callback,null);
   }
}
static class Program
{
   static void Main()
   {
      ServiceHost host = new ServiceHost(typeof(MyService));
      host.Open();

Application.Run(new MyForm());


      host.Close();
   }
}

Listing 1. Using the form synchronization context

The preceding listing shows the MyForm form that provides the MySynchronizationContext property, allowing its clients to obtain its synchronization context. MyForm initializes MySynchronizationContext in its constructor by obtaining the synchronization context of the current thread. MyForm also offers the Counter property that updates the value of a counting Windows Forms label. Counter must be accessed by the thread that owns the form.

The MyService service implements the IncrementLabel() operation. In that operation, the service obtains a reference to the form via the static OpenForms collection of the Application class. As soon as IncrementLabel() has the form to update, it accesses the synchronization context via the MySynchronizationContext, and then calls the Send() method. Send() is provided with an anonymous method that updates Counter.

Safe Controls

The technique that Listing 1 shows suffers from tight coupling between the service and the form. If the service must update multiple controls, that also results in a cumbersome programming model. Any change to the UI layout, the controls on the forms, and the required behavior is likely to cause major changes to the service code.

It is better to encapsulate the interaction with the Windows Forms synchronization context in safe controls or safe methods on the form, to decouple them from the service and simplify the overall programming model. Listing 2 lists the code for SafeLabel, a Label-derived class that provides a thread-safe access to its Text property. Because SafeLabel derives from Label, you still have all of the design-time visual experience and integration with Microsoft Visual Studio 2008, yet you surgically affect only the property that requires the safe access.

public class SafeLabel : Label
{
   SynchronizationContext m_SynchronizationContext=SynchronizationContext.Current;
   override public string Text
   {
      set
      {
         SendOrPostCallback setText = delegate(object text)
                                      {
                                         base.Text = text as string;
                                      };
         m_SynchronizationContext.Send(setText,value);
      }
      get
      {
         string text = String.Empty;
         SendOrPostCallback getText = delegate
                                      {
                                         text = base.Text;
                                      };
         m_SynchronizationContext.Send(getText,null);
         return text;
      }
   }
}

Listing 2. Encapsulating the synchronization context

Upon construction, SafeLabel caches its synchronization context. SafeLabel overrides its base-class Text property, and uses an anonymous method in the get and set accessors to send the call to the correct UI thread. In the get accessor, note the use of an outer variable to return a value from Send(). By using SafeLabel, the code that Listing 1 shows is reduced to the code that Listing 3 shows:

class MyForm : Form
{
   Label m_CounterLabel;

   public MyForm()
   {
      InitializeComponent();
   }
   void InitializeComponent()
   {
      ...
      m_CounterLabel = new SafeLabel();
      ...
   }
   public int Counter //Same as Listing 1
   {...}
}

class MyService : IFormManager
{
   public void IncrementLabel()
   {
      MyForm form = Application.OpenForms[0] as MyForm;
      Debug.Assert(form != null);

form.Counter++;

   }
}

Listing 3. Using a safe control

In the preceding listing, note that the service simply accesses the form directly:

form.Counter++;

and that the form is written as a normal form.

Note  In the assembly ServiceModelEx.dll, the source code that accompanies this article contains the code not only for SafeLabel, but also for other commonly used controls, such as SafeButton, SafeListBox, SafeProgressBar, SafeStatusBar, and SafeTextBox.

The programming techniques that we have shown thus far put the onus of accessing the UI thread squarely on the service or UI developer. It would be preferable if the service had a way of associating itself with a particular synchronization context, and then have WCF detect that context and automatically marshal the call from the worker thread to the service synchronization context. WCF lets you do just that. You can instruct WCF to maintain affinity between all of the service instances from a participle host and a specific synchronization context. The ServiceBehavior attribute offers the UseSynchronizationContext property, which is defined as follows:

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

Affinity between the service type, its host, and a synchronization context is locked in when the host is opened. If the thread that is opening the host has a synchronization context and UseSynchronizationContext is true, WCF will establish an affinity between that synchronization context and all of the instances of the service that is hosted by that host. WCF will automatically marshal all of the incoming calls to the service’s synchronization context.

If UseSynchronizationContext is false, regardless of any synchronization context that the opening thread might have, the service will have no affinity to any synchronization context. In much the same way, even if UseSynchronizationContext is true, if the opening thread has no synchronization context, the service will not have one.

The default value of UseSynchronizationContext is true, so that the following definitions are equivalent:

class MyService : IMyContract
{...}

[ServiceBehavior(UseSynchronizationContext = true)]
class MyService : IMyContract
{...}

Hosting on the UI Thread

The classic use of UseSynchronizationContext is to enable the service to update UI controls and windows directly, without resorting to techniques such as the ones that appear in Listing 1 and Listing 3. WCF greatly simplifies UI updates by providing an affinity between all of the service instances from a particular host and a specific UI thread. To that end, you should host the service on the UI thread that also creates the windows or controls with which the service must interact.

Because the Windows Forms synchronization context is established during the form’s construction, you could rearrange the Main() method that Listing 3 shows to have the form be constructed before opening the host, and, without any additional code, have WCF marshal automatically to the UI thread all of the calls to the service, as Listing 4 shows:

partial class MyForm : Form
{
   Label m_CounterLabel;

   public MyForm()
   {
      InitializeComponent();
   }
   public int Counter //Same as Listing 1
   {...}
}

//Same as Listing 3
class MyService : IFormManager
{...}

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

Listing 4. Automatic use of the form’s synchronization context

The problem with Listing 4 is that the service (and the form’s code) are at the mercy of the Main() method, and not every developer will know that the order of the lines in Main() matters that much.

The simple solution is to have the window or form with which the service must interact be the one that opens the host before loading the form, as Listing 5 shows:

class MyService : IFormManager
{
   public void IncrementLabel()
   {
      HostForm form = Application.OpenForms[0] as HostForm;
      Debug.Assert(form != null);
      form.Counter++;
   }
}
partial class HostForm : Form
{
   Label m_CounterLabel;
   ServiceHost m_Host;

   public HostForm()
   {
      InitializeComponent();

      m_Host = new ServiceHost(typeof(MyService));

m_Host.Open();

   }
   public int Counter//Same as Listing 1
   {...}

   void OnFormClosed(object sender,EventArgs e)
   {
      m_Host.Close();
   }
}
static class Program
{
   static void Main()
   {
      Application.Run(new HostForm());
   }
}

Listing 5. Hosting the service by the form

The service that Listing 5 shows defaults to using whichever synchronization context its host encounters. The HostForm form stores the service host in a member variable, so that the form can close the service when the form is closed. The constructor of HostForm already has a synchronization context, so that, when it opens the host, affinity to that synchronization context is established. The service can freely access the form directly, as opposed to Listing 1, which required marshaling.

Form as a Service

The main motivation for hosting a WCF service on the UI thread is if the service must update the UI or the form. The problem is always: How does the service reach out and obtain a reference to the form? While the techniques and ideas that appear thus far in the listings certainly work, it would be simpler yet if the form were the service and hosted itself. For this to work, the form (or any window) must be a singleton service. The reason is that singleton is the only instancing mode that enables you to provide WCF with a live instance to host. In addition, you would not want a per-call form that exists only during a client call (which is usually very brief), nor would you want a per-session form that only a single client can establish a session with and update.

When a form is also a service, having that form as a singleton service is the best instancing mode all around. Listing 6 shows just such a form:

[ServiceContract]
interface IFormManager
{
   [OperationContract]
   void IncrementLabel();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
partial class MyForm : Form,IFormManager
{
   Label m_CounterLabel;
   ServiceHost m_Host;

   public MyForm()
   {
      InitializeComponent();
      m_Host = new ServiceHost(this);
      m_Host.Open();
   }
   void OnFormClosed(object sender,EventArgs args)
   {
      m_Host.Close();
   }
   public void IncrementLabel()
   {
      Counter++;
   }
   public int Counter//Same as Listing 1
   {...}
}

Listing 6. Form as a singleton service

MyForm implements the IFormManager contract and is configured as a WCF singleton service. When MyForm constructs the host, it uses the host constructor that accepts an object reference. MyForm passes itself as the object. MyForm opens the host when the form is created and closes the host when the form is closed. Updating the form’s controls as a result of client calls is done by accessing them directly, because the form, of course, runs on its own synchronization context.

UI Thread and Concurrency Management

Whenever you use hosting on the UI thread, deadlocks are possible. For example, the following setup is guaranteed to result with a deadlock: A Windows Forms application is hosting a service with UseSynchronizationContext set to true, and UI thread affinity is established. The Windows Forms application then calls the service over one of its endpoints. The call to the service blocks the UI thread, while WCF posts a message to the UI thread to invoke the service. That message is never processed, because of the blocking UI thread—hence, the deadlock.

Another possible case for a deadlock occurs when a Windows Forms application is hosting a service with UseSynchronizationContext set to true and UI thread affinity is established. The service receives a call from a remote client. That call is marshaled to the UI thread and is eventually executed on that thread. If the service is allowed to call out to another service, that can result in a deadlock if the callout causality tries somehow to update the UI or call back to the service’s endpoint, because all of the service instances that are associated with any endpoint (regardless of the service-instancing mode) share the same UI thread.

Similarly, you risk a deadlock if the service is configured for reentrancy and it calls back to its client. You risk a deadlock if the callback causality tries to update the UI or enter the service, because that reentrance must be marshaled to the blocked UI thread.

UI Responsiveness

Every client call to a service that is hosted on the UI thread is converted to a Windows message and eventually executed on the UI thread—the same thread that is responsible for updating the UI, and for continuing to respond to the user input. While the UI thread is processing the service call, it does not process UI messages. Consequently, you should avoid lengthy execution in the service operation, because that can severely degrade the UI responsiveness. You can alleviate this somewhat by pumping Windows messages in the service operation by explicitly calling the static Application.DoEvents() method to process all of the queued-up Windows messages, or by using a method such as MessageBox.Show() that pumps some but not all of the queued messages. The downside of trying to refresh the UI this way is that it might dispatch client calls to the service that are queued, and can cause unwanted reentrancy or a deadlock.

To make things even worse, as a product of the service-concurrency mode (discussed next), even if the service calls are of short duration: What if a number of them are dispatched to the service all at once by clients? All of those calls will be queued back-to-back in the Windows message queue, and processing them in order might take time—all of the while, not updating the UI.

Whenever you are hosting on a UI thread, carefully examine the duration and frequency of calls to see if the resulting degradation in UI responsiveness is acceptable. Typically, the latency would be caused not by the UI updates, but instead by the performance of lengthy operations (such as calling other services) or computational-intensive operations (such as image processing). Because the service is hosted on the UI thread, WCF performs all of that work on the UI thread—not just the critical part that interacts with the UI directly. If that is indeed your situation, disallow the affinity to the UI thread altogether by setting UseSynchronizationContext to false, as follows:

[ServiceBehavior(UseSynchronizationContext = false)]
class MyService : IMyContract
{
   public void MyMethod()
   {
      Debug.Assert(Application.MessageLoop == false);
      //Rest of the implementation
   }
}

(You can even assert that the thread that is executing the service call does not have a message loop.) Perform the lengthy operations on the incoming worker thread, and use safe controls (such as SafeLabel) to marshal the calls to the UI thread—only when required, as opposed to all of the time.

UI Thread and Concurrency Modes

A service with a UI thread affinity is inherently thread-safe, because only the UI thread can ever call its instances. Because only a single thread (and the same thread, at that) can ever access an instance, that instance by definition is thread-safe. As a result, configuring the service with ConcurrencyMode.Single adds no safety, because the service is single-threaded anyway. When you configure with ConcurrencyMode.Single, concurrent client calls are first queued up by the instance lock and are dispatched to the service’s message loop one at a time, in order. These client calls, therefore, are given the opportunity of being interleaved with other UI Windows messages; thus, ConcurrencyMode.Single yields the best UI responsiveness, because the UI thread will alternate between processing client calls and user interactions.

When configured with ConcurrencyMode.Multiple, client calls are dispatched to the service message loop as soon as they arrive off the channel, and are invoked in order. The problem is that this allows for the possibility of a batch of client calls either back-to-back or in proximity to each other in the Windows message queue; and, when the UI thread processes that batch, the UI will be unresponsive. Consequently, ConcurrencyMode.Multiple is the worst for UI responsiveness.

When configured with ConcurrencyMode.Reentrant, the service is not reentrant at all, and deadlocks are still possible. Clearly, the best practice with UI thread affinity is to configure the service with ConcurrencyMode.Single. Avoid ConcurrencyMode.Multiple, because of its detrimental effect on responsiveness, as well as ConcurrencyMode.Reentrant, because of its unfulfilled safety.

There are quite a few cases in which a client might receive concurrent callbacks. If the client provided a callback reference to multiple services, those services could call back concurrently to the client. However, even with a single callback reference, the service might launch multiple threads and use all of them to call on that single reference. Duplex callbacks enter the client on worker threads and might corrupt the client state, if done concurrently without synchronization. The client must synchronize access to its own in-memory state, but also to any resources that the callback thread might access. Similarly to a service, a callback client can use the CallbackBehavior attribute with its ConcurrencyMode and UseSynchronizationContext properties:

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

Both of these properties default to the same values as with the ServiceBehavior attribute, and they behave in a similar manner.

The callback instance itself might require thread affinity for interacting with a UI thread. While the callback can use techniques such as those that Listing 1 and Listing 3 show to marshal the interaction to the UI synchronization context, you can also have WCF associate the callback with a particular synchronization context by setting the UseSynchronizationContext property to true. However, unlike the service, the client does not use any host to expose the endpoint. If the UseSynchronizationContext property is true, the synchronization context to use is locked-in when the proxy is opened—or, more commonly, when the client makes the first call to the service by using the proxy, if Open() is not called explicitly. If the calling client thread has a synchronization context, this will be the synchronization context that WCF will use for all of the callbacks to the client’s endpoint that are associated with that proxy.

Note that only the first call (or the call to Open()) that is made on the proxy is given the opportunity to determine the synchronization context. Subsequent calls have no say in the matter. If the calling client thread has no synchronization context, even if UseSynchronizationContext is true, no synchronization context will be used for the callbacks.

The common use for UI updates over callbacks is to have the form itself implement the callback contract and update the UI, as Listing 7 shows:

partial class MyForm : Form,IMyContractCallback
{
   MyContractClient m_Proxy;

   public MyForm()
   {
      InitializeComponent();
      InstanceContext callbackContext = new InstanceContext(this);
      m_Proxy = new MyContractClient(callbackContext);
   }
   //Called as a result of a UI event
   public void OnCallService(object sender,EventArgs args)
   {
      m_Proxy.DoSomething();//Affinity established here
   }
   //This method always runs on the UI thread
   void IMyContractCallback.OnCallback()
   {
      //No need for synchronization and marshaling
      Text = "Some Callback";
   }
   public void OnClose(object sender,EventArgs args)
   {
      m_Proxy.Close();
   }
}

Listing 7. Relying on the UI synchronization context for callbacks

In the preceding listing, the proxy is first used in the CallService() method, which is called by the UI thread as a result of some UI event. Calling the proxy on the UI synchronization context establishes the affinity to it, so that the callback can access and update the UI directly without marshaling any calls.

You can also explicitly establish the affinity to the UI synchronization context by opening the proxy in the form’s constructor without invoking an operation. This is especially useful if you want to dispatch calls to the service on worker threads (or, perhaps, even asynchronously), and yet have the callbacks enter on the UI synchronization context, as Listing 8 shows:

partial class MyForm : Form,IMyContractCallback
{
   MyContractClient m_Proxy;

   public MyForm()
   {
      InitializeComponent();
      InstanceContext callbackContext = new InstanceContext(this);
      m_Proxy = new MyContractClient(callbackContext);

      //Establish affinity to UI synchronization context here:

m_Proxy.Open();

   }
   public void OnCallService(object sender,EventArgs args)
   {
      ThreadStart invoke = delegate
                           {
                              m_Proxy.DoSomething();
                           };
      Thread thread = new Thread(invoke);
      thread.Start();
   }
   //This method always runs on the UI thread
   void IMyContractCallback.OnCallback()
   {
      //No need for synchronization and marshaling
      Text = "Some Callback";
   }
   public void OnClose(object sender,EventArgs args)
   {
      m_Proxy.Close();
   }
}

Listing 8. Opening a proxy explicitly to establish synchronization context

UI Thread Callbacks and Responsiveness

When callbacks are processed on the UI thread, the UI itself is not responsive. Even if you perform relatively short callbacks, if the callback is configured with ConcurrencyMode.Multiple, there could be multiple callbacks back-to-back in the UI message queue, and processing them all at once will degrade responsiveness. You should avoid lengthy callback processing on the UI thread and opt for configuring the callback with ConcurrencyMode.Single, so that the callback object lock will queue up the multiple callbacks; and, by dispatching them one at a time to the callback object, you enable interleaving them among the UI messages.

UI Thread Callbacks and Concurrency Management

Configuring the callback for affinity to the UI thread can trigger a deadlock. Consider the following setup: A Windows Forms client establishes affinity between a callback object (or even itself) and the UI synchronization context. The client then calls a service passing the callback reference. The service is configured for reentrancy, and it calls the client. A deadlock occurs now, because the callback to the client must execute on the UI thread, and that thread is blocked while waiting for the service call to return.

Configuring the callback as a one-way operation will not resolve the problem here, because the one-way call still must be marshaled first to the UI thread. The only way to resolve the deadlock in this case is to turn off use of the UI synchronization context by the callback, and to marshal the update to the form manually and asynchronously by using its synchronization context. Listing 9 demonstrates use of this technique:

////////////////////////// Client Side /////////////////////
[CallbackBehavior(UseSynchronizationContext = false)]
partial class MyForm : Form,IMyContractCallback
{
   SynchronizationContext m_Context;
   MyContractClient m_Proxy;

   public MyForm()
   {
      InitializeComponent();
      m_Context = SynchronizationContext.Current;
      InstanceContext callbackContext = new InstanceContext(this);
      m_Proxy = new MyContractClient(callbackContext);
   }

   public void CallService(object sender,EventArgs args)
   {
      m_Proxy.DoSomething();
   }
   //Callback runs on worker threads
   void IMyContractCallback.OnCallback()
   {
      SendOrPostCallback setText = delegate
                                   {
                                      Text = "Manually marshaling to UI thread";
                                   };
      m_Context.Post(setText,null);
   }
   public void OnClose(object sender,EventArgs args)
   {
      m_Proxy.Close();
   }
}
////////////////////////// Service Side /////////////////////
[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{
   [OperationContract]
   void DoSomething();
}
interface IMyContractCallback
{
   [OperationContract]
   void OnCallback();
}
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
class MyService : IMyContract
{
   public void DoSomething()
   {
      IMyContractCallback callback = OperationContext.Current.
                                         GetCallbackChannel<IMyContractCallback>();

callback.OnCallback();

   }
}

Listing 9. Avoiding callback deadlock on the UI thread

As the preceding listing shows, you must use the Post() method of the synchronization context. Under no circumstances should you use the Send() method; even though the callback is executing on the worker thread, the UI thread is still blocked on the outbound call. Calling Send() would trigger the deadlock you are trying to avoid, because Send() will block until the UI thread can process the request. In much the same way, the callback that Listing 9 shows cannot use any of the safe controls (such as SafeLabel), because those also use the Send() method.

When a client calls a service, the client is usually blocked while the service executes the call, and control returns to the client only when the operation completes its execution and returns. However, there are quite a few cases in which you want to call operations asynchronously; that is, you want control to return immediately to the client while the service executes the operation in the background, and then somehow let the client know that the method has completed execution, and provide the client with the results of the invocation. This is especially important if the client calls the service on the UI thread, because, if the call is synchronous, as long as the service executes the call, the UI is irresponsive.

Because the client decides if the call should be synchronous or asynchronous, you must create a different proxy for the asynchronous case. In Visual Studio 2008, when adding a service reference, click the Advanced button to bring up the Service Reference Settings dialog box, and then check the Generate asynchronous operations check box. This action instructs Visual Studio 2008 to generate a proxy that contains asynchronous methods in addition to the synchronous ones. For each operation in the original contract, the asynchronous proxy and contract will contain two additional methods of this form, as follows:

[OperationContract(AsyncPattern = true)]
IAsyncResult Begin<Operation>(<in arguments>,
                              AsyncCallback callback,object asyncState);
<returned type> End<Operation>(<out arguments>,IAsyncResult result);

The AsyncPattern property defaults to false. You can set AsyncPattern to true only on a method with a Begin<Operation>()–compatible signature, and the defining contract must also have a matching method with an End<Operation>()–compatible signature. These requirements are verified at the proxy load time. What AsyncPattern does is bind the underlying synchronous method with the Begin/End pair, and then correlate the synchronous execution with the asynchronous one.

Briefly, when the client invokes a method of the form Begin<Operation>(), with AsyncPattern set to true, it tells WCF not to try to invoke directly a method by that name on the service. Instead, it will use a thread from the thread pool to call the underlying method synchronously. The synchronous call will block the thread from the thread pool—not the calling client. The client will be blocked only for the slightest moment that it takes to dispatch the call request to the thread pool. The reply of the synchronous invocation is correlated with the End<Operation>() method.

Listing 10 shows a calculator contract and implementing service, and the generated proxy class with the asynchronous methods:

////////////////////////// Service Side //////////////////////
[ServiceContract]
interface ICalculator
{
   [OperationContract]
   int Add(int number1,int number2);
   //More operations
}
class Calculator : ICalculator
{
   public int Add(int number1,int number2)
   {
      return number1 + number2;
   }
   //Rest of the implementation
}
////////////////////////// Client Side //////////////////////
[ServiceContract]
interface ICalculator
{
   [OperationContract]
   int Add(int number1,int number2);

   [OperationContract(AsyncPattern = true)]
   IAsyncResult BeginAdd(int number1,int number2,AsyncCallback callback,
                                                                object asyncState);
   int EndAdd(IAsyncResult result);

   //Rest of the methods
}
public partial class CalculatorClient : ClientBase<ICalculator>,ICalculator
{ 
   public int Add(int number1,int number2)
   {
      return Channel.Add(number1,number2);
   }
   public IAsyncResult BeginAdd(int number1,int number2,
                                AsyncCallback callback,object asyncState)
   {
      return Channel.BeginAdd(number1,number2,callback,asyncState);
   }
   public int EndAdd(IAsyncResult result)
   {
      return Channel.EndAdd(result);
   }
   //Rest of the methods and constructors
}

Listing 10. Asynchronous contract and proxy

Asynchronous Invocation

Begin<Operation>() accepts the input parameters of the original synchronous operation. Begin<Operation>() accepts two additional input parameters that are not present in the original operation signature: callback and asyncState. The callback parameter is a delegate that targets a client-side method completed notification event. The asyncState parameter is an object that conveys whatever state information is needed by the party that handles the method completion.

Every Begin<Operation>() method returns an object that implements the IAsyncResult interface and uniquely identifies the method that was invoked by using Begin<Operation>(). You can pass the IAsyncResult object to End<Operation>() to identify the specific asynchronous method execution from which you wish to retrieve the results. End<Operation>() will block its caller until the operation for which it is waiting (identified by the IAsyncResult object passed in) completes and it reruns the results or errors. If the method is already complete by the time End<Operation>() is called, End<Operation>() will not block the caller, but will only return the results.

The easiest way of using WCF asynchronous calls is have the client provide WCF with a callback method and request that WCF call that method when the asynchronous method completes. Completion-callback methods by far are the preferred model in any event-driven UI application, because they avoid blocking the UI thread when trying to get the result of the asynchronous invocation. When the asynchronous method execution is complete, instead of quietly returning to the pool, the worker thread calls the completion callback. To designate a completion-callback method, the client must provide Begin<Operation>() with a delegate of the type AsyncCallback defined as follows:

public delegate void AsyncCallback(IAsyncResult asyncResult);

Completion-Callback Synchronization Context

The completion callback might require some thread or threads affinity or in general, to run in a particular synchronization context. This is especially the case if the completion callback must update some UI about the result of the asynchronous invocation. Unfortunately, you must marshal the call manually from the completion callback to the correct synchronization context, by using any of the techniques that we described previously.

Listing 11 demonstrates such a completion callback that interacts directly with its containing form, assuring that the UI update will be on the UI synchronization context:

partial class CalculatorForm : Form
{
   CalculatorClient m_Proxy;
   SynchronizationContext m_SynchronizationContext;

   public MyClient()
   {
      InitializeComponent();
      m_Proxy = new CalculatorClient();
      m_SynchronizationContext = SynchronizationContext.Current;
   }
   public void CallAsync(object sender,EventArgs args)
   {
      m_Proxy.BeginAdd(2,3,OnCompletion,null);
   }
   void OnCompletion(IAsyncResult result)
   {
      SendOrPostCallback callback = delegate
                                    {
                                       Text = "Sum = " + m_Proxy.EndAdd(result);
                                    };
      m_SynchronizationContext.Send(callback,null);
   }
   public void OnClose(object sender,EventArgs args)
   {
      m_Proxy.Close();
   }
}

Listing 11. Relying on completion-callback synchronization context

The problem with the technique that Listing 11 shows is that it requires a lot of the developer, and it pollutes the client’s code with managing the completion on the correct thread.

Visual Studio 2008 offers an alternative complementary programming model that lets the client receive an event on the correct thread. When generating the asynchronous methods, the proxy class will contain also a public event whose signature contains the result (or errors) of the asynchronous invocation. In addition to the methods that Listing 10 shows, the proxy class will have the following members:

class CalculatorClient : ClientBase<ICalculator>,ICalculator
{
   public event EventHandler<AddCompletedEventArgs> AddCompleted;

   public void AddAsync(int number1,int number2);

   //Rest of the proxy
}

It is up to the client to wire up a completion method to that event. Unlike in Listing 11, this completion method will be called on the synchronization context of the thread that is used to dispatch the asynchronous call. This is exactly what the UI client needs; you can subscribe a completion method to the event, then dispatch the call on the UI thread, and have the event firing be marshaled automatically to the UI thread where the completion-callback method will execute. Listing 12 shows this technique:

partial class CalculatorForm : Form
{
   CalculatorClient m_Proxy;
   public MyClient()
   {
      InitializeComponent();

      m_Proxy = new CalculatorClient();
      m_Proxy.AddCompleted += (sender,args)=>
                              {
                                 //Called on the UI thread
                                 Text = "Sum = " + args.Result;
                              };
   void CallAsync(object sender,EventArgs args)
   {
      m_Proxy.AddAsync(2,3);//Sync context picked up here
   }
}

Listing 12. Completion callback on the UI thread via event handling

About the author

Juval Lowy is a software architect and the principal of IDesign, Inc. (www.idesign.net) He specializes in .NET Framework 3.0 architecture consulting and advanced .NET Framework 3.0 training. Juval is Microsoft’s Regional Director for the Silicon Valley. This article contains excerpts from his latest book, “Programming WCF Services” (Sebastopol, CA: O’Reilly, 2007). Juval participates in the Microsoft internal design reviews for future versions of .NET Framework and related technologies. He has published numerous articles, regarding almost every aspect of .NET Framework development, and is a frequent presenter at development conferences. Microsoft has recognized Juval as a Software Legend as one of the world’s top .NET Framework experts and industry leaders.

Show:
© 2014 Microsoft