Service Synchronization Context

The programming techniques shown so far put the onus of accessing the resource on the correct thread squarely on the service or resource developer. It would be preferable if the service had a way of associating itself with a particular synchronization context, and could have WCF detect that context and automatically marshal the call from the worker thread to the associated service synchronization context. In fact, WCF lets you do just that. You can instruct WCF to maintain an affinity between all service instances from a particular host and a specific synchronization context. The ServiceBehavior attribute offers the UseSynchronizationContext Boolean property, defined as:

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

The affinity between the service type, its host, and a synchronization context is locked in when the host is opened. If the thread opening the host has a synchronization context and UseSynchronizationContext is true, WCF will establish an affinity between that synchronization context and all instances of the service hosted by that host. WCF will automatically marshal all incoming calls to the service’s synchronization context. All the thread-specific information stored in the TLS, such as the client’s transaction or the security information (discussed in Chapter 10), will be marshaled correctly to the synchronization context.

If UseSynchronizationContext is false, regardless of any synchronization context the opening thread might have, the service will have no affinity to any synchronization context. Likewise, even if UseSynchronizationContext is true, if the opening thread has no synchronization context the service will not have one either.

The default value of UseSynchronizationContext is true, so these definitions are equivalent:

[ServiceContract]
interface IMyContract
{...}

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

Hosting on the UI Thread

Again, I will use the UI thread affinity as a model for demonstrating the way WCF is integrated with synchronization context, but as you will see later on, the discussion here is just as relevant with more powerful examples, especially those involving custom synchronization contexts.

You can use the UseSynchronizationContext property to enable the service to update user interface controls and windows directly, without resorting to techniques such as those illustrated in Example 8-6 and Example 8-7. WCF greatly simplifies UI updates by providing an affinity between all service instances from a particular host and a specific UI thread. To achieve that end, host the service on the UI thread that also creates the windows or controls with which the service needs to interact. Since the Windows Forms synchronization context is established during the instantiation of the base window, you need to open the host before that. For example, this sequence from Example 8-6:

ServiceHost host = new ServiceHost(typeof(MyService));
host.Open();

Application.Run(new MyForm());

will not have the host associate itself with the form synchronization context, since the host is opened before the form is created.

However, this minute change in the order of the lines of instantiation will achieve the desired effect:

Form form = new MyForm();

ServiceHost host = new ServiceHost(typeof(MyService));
host.Open();

Application.Run(form);

Although this change has no apparent effect in classic .NET, it is actually monumental for WCF, since now the thread that opened the host does have a synchronization context, and the host will use it for all calls to the service. The problem with this approach is that it is fragile—most developers maintaining your code will not be aware that simply rearranging the same independent lines of code will have this effect. It is also wrong to design the form and the service that needs to update it so that they are both at the mercy of the Main() method and the hosting code to such a degree.

The simple solution is to have the window (or the thread-sensitive resource) that the service needs to interact with be the one that opens the host, as shown in Example 8-9.

Example 8-9. The form hosting the service

class MyService : IMyContract
{...}

partial class HostForm : Form
{
   ServiceHost m_Host;
   Label m_CounterLabel;

   public HostForm()
   {
      InitializeComponent();

      m_Host = new ServiceHost(typeof(MyService));

      m_Host.Open();
   }
   void OnFormClosed(object sender,EventArgs e)
   {
      m_Host.Close();
   }

   public int Counter
   {
      get
      {
         return Convert.ToInt32(m_CounterLabel.Text);
      }
      set
      {
         m_CounterLabel.Text = value.ToString();
      }
   }
}
static class Program
{
   static void Main()
   {
      Application.Run(new HostForm());
   }
}

The service in Example 8-9 defaults to using whichever synchronization context its host encounters. The form HostForm 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 when it opens the host, an affinity to that synchronization context is established.

Accessing the form

Even though the form hosts the service in Example 8-9, the service instances must have some proprietary application-specific mechanism to reach into the form. If a service instance needs to update multiple forms, you can use the Application.OpenForms collections (as in Example 8-6) to find the correct form. Once the service has the form, it can freely access it directly, as opposed to the code in Example 8-6, which required marshaling:

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

You could also store references to the forms to use in static variables, but the problem with such global variables is that if multiple UI threads are used to pump messages to different instances of the same form type, you cannot use a single static variable for each form type—you need a static variable for each thread used, which complicates things significantly.

Instead, the form (or forms) can store a reference to itself in the TLS, and have the service instance access that store and obtain the reference. However, using the TLS is a cumbersome and non-type-safe programming model. An improvement on this approach is to use thread-relative static variables. By default, static variables are visible to all threads in an app domain. With thread-relative static variables, each thread in the app domain gets its own copy of the static variable. You use the ThreadStaticAttribute to mark a static variable as thread-relative. Thread-relative static variables are always thread-safe because they can be accessed only by a single thread and because each thread gets its own copy of the static variable. Thread-relative static variables are stored in the TLS, yet they provide a type-safe, simplified programming model. Example 8-10 demonstrates this technique.

Example 8-10. Storing form reference in a thread-relative static variable

partial class HostForm : Form
{
   Label m_CounterLabel;
   ServiceHost m_Host;

   [ThreadStatic]
   static HostForm m_CurrentForm;

   public static HostForm CurrentForm
   {
      get
      {
         return m_CurrentForm;
      }
      private set
      {
         m_CurrentForm = value;
      }
   }
   public int Counter
   {
      get
      {
         return Convert.ToInt32(m_CounterLabel.Text);
      }
      set
      {
         m_CounterLabel.Text = value.ToString();
      }
   }
   public HostForm()
   {
      InitializeComponent();

      CurrentForm = this;

      m_Host = new ServiceHost(typeof(MyService));
      m_Host.Open();
   }
   void OnFormClosed(object sender,EventArgs e)
   {
      m_Host.Close();
   }
}
[ServiceContract]
interface IFormManager
{
   [OperationContract]
   void IncrementLabel();
}
class MyService : IFormManager
{
   public void IncrementLabel()
   {
      HostForm form = HostForm.CurrentForm;
      form.Counter++;
   }
}
static class Program
{
   static void Main()
   {
      Application.Run(new HostForm());
   }
}

The form HostForm stores a reference to itself in a thread-relative static variable called m_CurrentForm. The service accesses the static property CurrentForm and obtains a reference to the instance of HostForm on that UI thread.

Multiple UI threads

Your service host process can actually have multiple UI threads, each pumping messages to its own set of windows. Such a setup is usually required with UI-intensive applications that want to avoid having multiple windows sharing a single UI thread and hosting the services, because while the UI thread is processing a service call (or a complicated UI update), not all of the windows will be responsive. Since the service synchronization context is established per host, if you have multiple UI threads you will need to open a service host instance for the same service type on each UI thread. Each service host will therefore have a different synchronization context for its service instances. As mentioned in Chapter 1, in order to have multiple hosts for the same service type, you must provide each host with a different base address. The easiest way of doing that is to provide the form constructor with the base address to use as a construction parameter. I also recommend in such a case to use base address-relative addresses for the service endpoints. The clients will still invoke calls on the various service endpoints, yet each endpoint will now correspond to a different host, according to the base address schema and the binding used. Example 8-11 demonstrates this configuration.

Example 8-11. Hosting on multiple UI threads

partial class HostForm : Form
{
   public HostForm(string baseAddress)
   {
      InitializeComponent();

      CurrentForm = this;

      m_Host = new ServiceHost(typeof(MyService),new Uri(baseAddress));
      m_Host.Open();
   }
   //Rest same as Example 8-10
}
static class Program
{
   static void Main()
   {
      ParameterizedThreadStart threadMethod = (baseAddress)=>
                                              {
                                            string address = baseAddress as string;
                                            Application.Run(new HostForm(address));
                                              };
      Thread thread1 = new Thread(threadMethod);
      thread1.Start("http://localhost:8001/");

      Thread thread2 = new Thread(threadMethod);
      thread2.Start("http://localhost:8002/");
   }
}
/* MyService same as Example 8-10 */

////////////////////////////// Host Config File //////////////////////////////
<services>
   <service name = "MyService">
      <endpoint
         address  = "MyService"
         binding  = "basicHttpBinding"
         contract = "IFormManager"
      />
   </service>
</services>
////////////////////////////// Client Config File ////////////////////////////
<client>
   <endpoint name = "Form A"
      address  = "http://localhost:8001/MyService/"
      binding  = "basicHttpBinding"
      contract = "IFormManager"
   />
   <endpoint name = "Form B"
      Address  = "http://localhost:8002/MyService/"
      binding  = "basicHttpBinding"
      contract = "IFormManager"
   />
</client>

In Example 8-11, the Main() method launches two UI threads, each with its own instance of HostForm. Each form instance accepts as a construction parameter a base address that it in turn provides for its own host instance. Once the host is opened, it establishes an affinity to that UI thread’s synchronization context. Calls from the client to the corresponding base address are now routed to the respective UI thread.

A Form as a Service

The main motivation for hosting a WCF service on a UI thread is when the service needs to update the UI or the form. The problem is, how does the service reach out and obtain a reference to the form? While the techniques and ideas shown in the examples so far certainly work, the separation between the service and the form is artificial. It would be simpler 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, it wouldn’t be desirable to use a per-call form that exists only during a client call (which is usually very brief), or a sessionful 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 is the best instancing mode all around. Example 8-12 lists just such a service.

Example 8-12. Form as a singleton service

[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
   {
      get
      {
         return Convert.ToInt32(m_CounterLabel.Text);
      }
      set
      {
         m_CounterLabel.Text = value.ToString();
      }
   }
}

MyForm implements the IFormManager contract and is configured as a WCF singleton service. MyForm has a ServiceHost as a member variable, as before. When MyForm constructs the host, it uses the host constructor that accepts an object reference, as shown in Chapter 4. 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 in its own synchronization context.

The FormHost<F> class

You can streamline and automate the code in Example 8-12 using my FormHost<F> class, defined as:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public abstract class FormHost<F> : Form where F : Form
{
   public FormHost(params string[] baseAddresses);

   protected ServiceHost<F> Host
   {get;}
}

Using FormHost<F>, Example 8-12 is reduced to:

partial class MyForm : FormHost<MyForm>,IFormManager
{
   Label m_CounterLabel;

   public MyForm()
   {
      InitializeComponent();
   }
   public void IncrementLabel()
   {
      Counter++;
   }
   public int Counter
   {
      get
      {
         return Convert.ToInt32(m_CounterLabel.Text);
      }
      set
      {
         m_CounterLabel.Text = value.ToString();
      }
   }
}

Note

The Windows Forms designer is incapable of rendering a form that has an abstract base class, let alone one that uses generics. You will have to change the base class to Form for visual editing, then revert to FormHost<F> for debugging. To compensate, copy the Debug configuration into a new solution configuration called Design, then add the DESIGN symbol to the Design configuration. Finally, define the form to render properly in design mode and to execute properly in debug and release modes:

#if DESIGN
public partial class MyForm : Form,IFormManager
#else
public partial class MyForm : FormHost<MyForm>,IFormManager
#endif
{...}

Example 8-13 shows the implementation of FormHost<F>.

Example 8-13. Implementing FormHost<F>

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public abstract class FormHost<F> : Form where F : Form
{
   protected ServiceHost<F> Host
   {get;private set;}

   public FormHost(params string[] baseAddresses)
   {
      Host = new ServiceHost<F>(this as F,baseAddresses);

      Load += delegate
              {
                 if(Host.State == CommunicationState.Created)
                 {
                    Host.Open();
                 }
              };
      FormClosed += delegate
                    {
                       if(Host.State == CommunicationState.Opened)
                       {
                          Host.Close();
                       }
                    };
   }
}

FormHost<F> is an abstract generic class configured as a singleton service. It takes a single type parameter, F, which is constrained to be a Windows Forms Form class. FormHost<F> uses my ServiceHost<T> as a member variable, specifying F for the type parameter for the host. FormHost<F> offers the host access to the derived forms, mostly for advanced configuration, so the Host property is marked as protected. The constructor of FormHost<F> creates the host, but does not open it. The reason is that the subform may want to perform some host initialization, such as configuring a throttle, and this initialization can only be done before opening the host. The subclass should place that initialization in its own constructor:

public MyForm()
{
   InitializeComponent();
   Host.SetThrottle(10,20,1);
}

To allow for this, the constructor uses an anonymous method to subscribe to the form’s Load event, where it first verifies that the subform has not yet opened the host and then opens it. In a similar manner, the constructor subscribes to the form’s FormClosed event, where it closes the host.

The UI Thread and Concurrency Management

Whenever you use hosting on the UI thread (or in any other case of a single-thread affinity synchronization context), deadlocks are possible. For example, the following setup is guaranteed to result in 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 in-proc 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 due to 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 established. The service receives a call from a remote client, which is marshaled to the UI thread and eventually executed on that thread. If the service is allowed to call out to another service, that may result in a deadlock if the callout causality tries somehow to update the UI or call back to the service’s endpoint, since all service instances 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: a deadlock will occur if the callback causality tries to update the UI or enter the service, since that reentrance must be marshaled to the blocked UI thread.

UI responsiveness

Every client call to a service hosted on the UI thread is converted to a Windows message and is eventually executed on the UI thread—the same thread that is responsible for updating the UI and for continuing to respond to user input, as well as updating the user about the state of the application. While the UI thread is processing the service call, it does not process UI messages. Consequently, you should avoid lengthy processing in the service operation, because that can severely degrade the UI’s responsiveness. You can alleviate this somewhat by pumping Windows messages in the service operation, either by explicitly calling the static method Application.DoEvents() to process all 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 may dispatch queued client calls to the service instance and may cause unwanted reentrancy or a deadlock.

To make things even worse, what if clients dispatch a number of calls to the service all at once? Depending on the service concurrency mode (discussed next) even if those service calls are of short duration, the calls will all be queued back-to-back in the Windows message queue, and processing them in order might take time—and all the while, the UI will not be updated.

Whenever you’re hosting on a UI thread, carefully examine the calls’ duration and frequency to see whether the resulting degradation in UI responsiveness is acceptable. What is acceptable may be application-specific, but as a rule of thumb, most users will not mind a UI latency of less than half a second, will notice a delay of more than three quarters of a second, and will be annoyed if the delay is more than a second. If that is the case, consider hosting parts of the UI (and the associated services) on multiple UI threads, as explained previously. By having multiple UI threads you maximize responsiveness, because while one thread is busy servicing a client call, the rest can still update their windows and controls. If using multiple UI threads is impossible in your application and processing service calls introduces unacceptable UI responsiveness, examine what the service operations do and what is causing the latency. Typically, the latency would be caused not by the UI updates but rather by performing 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:

[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 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 the time. The downside of this approach is that it is an expert programming model: the service cannot be the window or form itself (by relying on the simplicity of FormHost<F>), so you need a way of binding to the form, and the service developer has to work together with the UI developers to ensure they use the safe controls or provide access to the form’s synchronization context.

The UI thread and concurrency modes

A service with a UI thread affinity is inherently thread-safe because only that UI thread can ever call its instances. Since only a single thread (and the same thread, at that) can ever access an instance, that instance is by definition thread-safe. Since the service is single-threaded anyway, configuring the service with ConcurrencyMode.Single adds no safety. When you configure with ConcurrencyMode.Single, concurrent client calls are first queued up by the instance lock and then dispatched to the service’s message loop one at a time, in order. These client calls are therefore given the opportunity of being interleaved with other UI Windows messages. ConcurrencyMode.Single thus yields the best responsiveness, because the UI thread will alternate between processing client calls and user interactions. When you configure the service 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 mode allows the possibility of a batch of client calls arriving either back-to-back or in close proximity to one another in the Windows message queue, and while the UI thread processes that batch, the UI will be unresponsive. Consequently, ConcurrencyMode.Multiple is the worst option for UI responsiveness. When configured with ConcurrencyMode.Reentrant, the service is not reentrant at all, and deadlocks are still possible, as explained at the beginning of this section. Clearly, the best practice with UI thread affinity is to configure the service with ConcurrencyMode.Single. Avoid ConcurrencyMode.Multiple due to its detrimental effect on responsiveness and ConcurrencyMode.Reentrant due to its unfulfilled ability.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.94.249