Section 8.6. Service Synchronization Context


8.6. Service Synchronization Context

The programming techniques showed 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 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 service instances from a participle 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 } 

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. Much the same way, even if UseSynchronizationContext is true, if the opening thread has no synchronization context, then the service will not have one.

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

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

8.6.1. Hosting on the UI Thread

The classic use for UseSynchronizationContext is to enable the service to update user interface controls and windows directly, without resorting to techniques such as Examples 8-6 and 8-7. WCF greatly simplifies UI updates by providing an affinity between all service instances from a particular host and specific UI thread. To that end, host the service on the UI thread that also creates the windows or controls that the service needs to interact with. Since the Windows Forms synchronization context is established during the message pump initialization of Application.Run( ), which is a blocking operation (that brings up the window or form), opening the host before or after Application.Run( ) is pointlessbefore Application.Run( ), there is still no synchronization context, and after it the application is usually shutting down. The simple solution is to have the window or form that the service needs to interact with be the one that opens the host before loading the form, as shown in Example 8-9.

Example 8-9. Hosting the service by the form

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyService : IMyContract {...} partial class HostForm : Form {    ServiceHost m_Host;    public HostForm( )    {       InitializeComponent( );       m_Host = new ServiceHost(typeof(MyService));       m_Host.Open( );    }    void OnFormClosed(object sender,EventArgs e)    {       m_Host.Close( );    } } 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, affinity to that synchronization context is established.

8.6.1.1. Accessing the form

Even though the service in Example 8-9 is hosted by the form, the service instances must have some proprietary application-specific mechanism to reach into the form. If the 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 Example 8-6, which required marshaling:

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] 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. The problem with such global variables is that if multiple UI threads are used to pump messages to difference instances of the same form type, you cannot use a single static variable for each form typeyou 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. The problem with the TLS is that it is a cumbersome 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 over the TLS. 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;       }       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( ); } [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] 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.

8.6.1.2. 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 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 still invoke calls on the various service endpoints, yet each endpoint now corresponds 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 = delegate(object 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 = "MyNamespace.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. Note that the service exposes an endpoint over HTTP using BasicHttpBinding. If the service were to expose a second endpoint over the TCP binding you would have to provide the host with a TCP base address as well.

8.6.2. Form As a Service

The main motivation for hosting a WCF service on the UI thread is if the service needs to 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 shown in the examples so far certainly work, it would be simpler yet if the form were the service and would host 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 form that only exists during a client call (which is usually very brief) nor would you want a 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, same 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 on its own synchronization context.

8.6.2.1. 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;set;} } 

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

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 back to FormHost<F> for debugging. Hopefully, these annoying deficiencies will be addressed in the future.


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 {    ServiceHost<F> m_Host;    protected ServiceHost<F> Host    {       get       {          return m_Host;       }       set       {          m_Host = value;       }    }    public FormHost(params string[] baseAddresses)    {       m_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 class configured as a singleton service. FormHost<F> is a generic class and it takes a single type parameter F. F 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 access to the host 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. 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 it was not opened yet by the subform, and then opens the host. In a similar manner, the constructor subscribes to the form's FormClosed event, where it closes the host.

8.6.3. 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 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 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 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 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. You risk a deadlock if the callback causality tries to update the UI or enter the service, since that reentrance must be marshaled to the blocked UI thread.

8.6.3.1. 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 the user input as well as updating the UI and 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 execution in the service operation because that can severely degrade the UI responsiveness. You can somewhat alleviate this by pumping Windows messages in the service operation 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 client calls to the service instance that are queued and may 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? Those calls will all be queued back-to-back in the Windows message queue, and processing them in order might take time, all the while not updating the UI. Whenever hosting on a UI thread, carefully examine the calls' duration and their frequency to see if 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 the 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 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(InstanceContextMode = InstanceContextMode.PerCall,                  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 just 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.

8.6.3.2. 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. Since only a single thread (and the same thread at that) can ever access an instance, that instance is by definition 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 are therefore given the opportunity of being interleaved with other UI Windows messages, and so ConcurrencyMode.Single yields the best 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 it allows 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, 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 safety.




Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net