Resource Synchronization Context

Incoming service calls execute on worker threads from the I/O completion thread pool and are unrelated to any service or resource threads. This means that by default the service cannot rely on any kind of thread affinity (that is, always being accessed by the same thread). Much the same way, the service cannot by default rely on executing on any host-side custom threads created by the host or service developers. The problem with this situation is that some resources may rely on thread affinity. For example, user interface resources updated by the service must execute and be accessed only by the user interface (UI) thread. Other examples are a resource (or a service) that makes use of the thread local storage (TLS) to store out-of-band information shared globally by all parties on the same thread (using the TLS mandates use of the same thread), or accessing components developed using legacy Visual Basic or Visual FoxPro, which also require thread affinity (due to their own use of the TLS). In addition, for scalability and throughput purposes, some resources or frameworks may require access by their own pool of threads.

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 it accesses.

.NET Synchronization Contexts

.NET 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 calls to that context. The synchronization context can be a single thread or any number of designated threads, although typically it will be just a single, yet particular thread. All the synchronization context does is assure that the call executes on the correct thread or threads.

Note that the word context is overloaded. Synchronization contexts have absolutely nothing to do with the service instance context or the operation context described so far in this book. They are simply the synchronizational context of the call.

While synchronization contexts are a simple enough design pattern to use conceptually, implementing a synchronization context is a complex programming task that is not normally intended for developers to attempt.

The SynchronizationContext class

The SynchronizationContext class from the System.Threading namespace represents a synchronization context:

public delegate void SendOrPostCallback(object state);

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

Every thread in .NET may 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. You can also pass the reference to the synchronization context between threads, so that one thread can marshal a call to another thread.

To represent the call to invoke in the synchronization context, you wrap a method with a delegate of the type SendOrPostCallback. Note that the signature of the delegate uses an object. If you want to pass multiple parameters, pack those in a structure and pass the structure as an object.

Warning

Synchronization contexts use an amorphous object. Exercise caution when using synchronization contexts, due to the lack of compile-time type safety. Instead of an object, you can use anonymous methods and outer variables (closures) that are type-safe.

Working with the synchronization context

There are two ways of marshaling a call to the synchronization context: synchronously and asynchronously, by sending or posting a work item, respectively. The Send() method will block the caller until the call has completed in the other synchronization context, while Post() will merely dispatch it to the synchronization context and then return control to its caller.

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

//Obtain synchronization context
SynchronizationContext context = ...

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

Example 8-4 shows a less abstract example.

Example 8-4. Calling a resource on the correct synchronization context

class MyResource
{
   public int DoWork()
   {...}
   public SynchronizationContext MySynchronizationContext
   {get;}
}
class MyService : IMyContract
{
   MyResource GetResource()
   {...}

   public void MyMethod()
   {
      MyResource resource = GetResource();
      SynchronizationContext context = resource.MySynchronizationContext;
      int result = 0;
      SendOrPostCallback doWork = _=>
                                  {
                                     result = resource.DoWork();
                                  };
      context.Send(doWork,null);
   }
}

In Example 8-4, the service MyService needs to interact with the resource MyResource and have it perform some work by executing the DoWork() method and returning a result. However, MyResource requires that all calls to it execute on its particular synchronization context. MyResource makes that execution context available via the MySynchronizationContext property. The service operation MyMethod() executes on a WCF worker thread. MyMethod() first obtains the resource and its synchronization context, then defines a Lambda expression that wraps the call to DoWork() and assigns that expression to the doWork delegate of the type SendOrPostCallback. Finally, MyMethod() calls Send() and passes null for the argument, since the DoWork() method on the resource requires no parameters. Note the technique used in Example 8-4 to retrieve a returned value from the invocation. Since Send() returns void, the Lambda expression assigns the returned value of DoWork() into an outer variable.

The problem with Example 8-4 is the excessive degree of coupling between the service and the resource. The service needs to know that the resource is sensitive to its synchronization context, obtain the context, and manage the execution. You must also duplicate such code in any service using the resource. It is much better to encapsulate the need in the resource itself, as shown in Example 8-5.

Example 8-5. Encapsulating the synchronization context

class MyResource
{
   public int DoWork()
   {
      int result = 0;
      SendOrPostCallback doWork = _=>
                                  {
                                     result = DoWorkInternal();
                                  };
      MySynchronizationContext.Send(doWork,null);
      return result;
   }
   SynchronizationContext MySynchronizationContext
   {get;}
   int DoWorkInternal()
   {...}
}
class MyService :  IMyContract
{
   MyResource GetResource()
   {...}
   public void MyMethod()
   {
      MyResource resource = GetResource();
      int result = resource.DoWork();
   }
}

Compare Example 8-5 to Example 8-4. All the service in Example 8-5 has to do is access the resource: it is up to the service internally to marshal the call to its synchronization context.

The UI Synchronization Context

The best way to illustrate the usefulness and power of synchronization contexts, along with concrete examples of the abstract patterns discussed so far, is when utilizing synchronization contexts along with Windows user interface frameworks such as Windows Forms or the Windows Presentation Foundation (WPF). Note that while this discussion focuses on the UI case, the patterns, design guidelines, and consequences of those design decisions and even the best practices apply to most other cases of a synchronization context.

For simplicity’s sake, the rest of the discussion in this chapter will refer only to Windows Forms, although it applies equally to WPF. A Windows UI application relies on the underlying Windows messages and a message-processing loop (the message pump) 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 need to update some user interface as a result of client calls or some other event. Fortunately, Windows Forms supports the synchronization context pattern. Every thread that pumps Windows messages has a synchronization context. That synchronization context is the WindowsFormsSynchronizationContext class:

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

Whenever you create any Windows Forms control or form, that control or form ultimately derives from the class Control. The constructor of Control checks whether the current thread that creates it already has a synchronization context, and if it does not, Control installs WindowsFormsSynchronizationContext as the current thread’s synchronization context.

WindowsFormsSynchronizationContext converts the call to Send() or Post() to a custom Windows message and posts that Windows message to the UI thread’s message queue. Every Windows Forms UI class that derives from Control has a dedicated method that handles this custom message by invoking the supplied SendOrPostCallback delegate. At some point, the UI thread processes the custom Windows message 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 needs to update a user interface, it must have some proprietary mechanisms to find the window to update in the first place. And once the service has the correct window, it must somehow get hold of that window’s synchronization context and marshal the call to it. Such a possible interaction is shown in Example 8-6.

Example 8-6. Using the form synchronization context

partial class MyForm : Form
{
   Label m_CounterLabel;
   public readonly SynchronizationContext MySynchronizationContext;

   public MyForm()
   {
      InitializeComponent();
      MySynchronizationContext = SynchronizationContext.Current;
   }
   void InitializeComponent()
   {
      ...
      m_CounterLabel = new Label();
      ...
   }

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

Example 8-6 shows the form MyForm, which provides the MySynchronizationContext property that allows its clients to obtain its synchronization context. MyForm initializes MySynchronizationContext in its constructor by obtaining the synchronization context of the current thread. The thread has a synchronization context because the constructor of MyForm is called after the constructor of its topmost base class, Control, was called, and Control has already attached the Windows Forms synchronization context to the thread in its constructor.

MyForm also offers a Counter property that updates the value of a counting Windows Forms label. Only the thread that owns the form can access that label. MyService implements the IncrementLabel() operation. In that operation, the service obtains a reference to the form via the static OpenForms collection of the Application class:

public class FormCollection : ReadOnlyCollectionBase
{
   public virtual Form this[int index]
   {get;}
   public virtual Form this[string name]
   {get;}
}

public sealed class Application
{
   public static FormCollection OpenForms
   {get;}
   //Rest of the members
}

Once IncrementLabel() has the form to update, it accesses the synchronization context via the MySynchronizationContext property and calls the Send() method. Send() is provided with an anonymous method that accesses the Counter property. Example 8-6 is a concrete example of the programming model shown in Example 8-4, and it suffers from the same deficiency: namely, tight coupling between all service operations and the form. If the service needs to update multiple controls, that also results in a cumbersome programming model. Any change to the user interface layout, the controls on the forms, and the required behavior is likely to cause major changes to the service code.

Safe controls

A better approach is 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 to simplify the overall programming model. Example 8-7 lists the code for SafeLabel, a Label-derived class that provides thread-safe access to its Text property. Because SafeLabel derives from Label, you still have full design-time visual experience and integration with Visual Studio, yet you can surgically affect just the property that requires the safe access.

Example 8-7. Encapsulating the synchronization context

public class SafeLabel : Label
{
   SynchronizationContext m_SynchronizationContext =
                                                   SynchronizationContext.Current;
   override public string Text
   {
      set
      {
         m_SynchronizationContext.Send(_=> base.Text = value,null);
      }
      get
      {
         string text = String.Empty;
         m_SynchronizationContext.Send(_=> text = base.Text,null);
         return text;
      }
   }
}

Upon construction, SafeLabel caches its synchronization context. SafeLabel overrides its base class’s Text property and uses the Lambda expression method in the get and set accessors to send the call to the correct UI thread. Note in the get accessor the use of an outer variable to return a value from Send(), as discussed previously. Using SafeLabel, the code in Example 8-6 is reduced to the code shown in Example 8-8.

Example 8-8. Using a safe control

class MyForm : Form
{
   Label m_CounterLabel;

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

      form.Counter++;
   }
}

Note in Example 8-8 that the service simply accesses the form directly:

form.Counter++;

and that the form is written as a normal form. Example 8-8 is a concrete example of the programming model shown in Example 8-5.

ServiceModelEx contains not only SafeLabel, but also other controls you are likely to update at runtime such as SafeButton, SafeListBox, SafeProgressBar, SafeStatusBar, SafeTrackBar, and SafeTextBox.

Warning

Exercise caution when using the safe controls (or in the general case, safe resources that encapsulate their own synchronization context). While having safe resources does simplify accessing each individual resource, if you have to access multiple resources, you will pay the penalty of marshaling to the synchronization context with every one of them. With multiple resources, it is better to lump all the accesses into a single method and marshal just the call to that method to the target synchronization context.

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

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