Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Thread-safe Silverlight Cairngorm

0.00/5 (No votes)
20 Oct 2008 1  
Some code changes and improvements that make the Silverlight Cairngorm thread-safe
SilverlightCairngorm

Introduction

In my previous article, Silverlight Cairngorm, I provided the essential Cairngorm constructs in Silverlight, like the abstract ModelLocator, FrontController, CairngormDelegate, and CairngormEventDispatcher, etc. Since it is ported from Flex ActionScript, and Flex doesn't support multi-threaded programming directly, it has no considerations on threading related issues. If the RIA you're planning follows a similar programming model as Flex does, not creating a worker thread or a thread-pool thread to perform background process, then using the previous version of the Silverlight Cairngorm is sufficient, you don't need to worry about threading and synchronizations.

However, if your Silverlight application is multi-threaded, it would require the framework --- Silverlight Cairngorm to be thread-safe. Specially, ModelLocator singleton creation and access needs to be thread safe, and all data binding event dispatching in the base abstract classes needs to be raised on the correct thread, or an exception will be thrown, or a wrong object reference created. This article details the improvements made on the previous version of the Silverlight Cairngorm in order to make it thread-safe, and also provides an updated demo project that uses a thread-pool thread to perform data transformations and data binding updates.

Thread-safe Singletons in the Silverlight Cairngorm

Within the Silverlight Cairngorm framework, CairngormEventDispatcher is an internal singleton object. Although it's not accessible by the application code, it's still important to be thread-safe in order to make sure the dispatched CairngormEvent is correctly routed to the associated command. Here is the common not-thread-safe singleton code for CairngormEventDispatcher:

namespace SilverlightCairngorm.Control
{
    /// <summary>
    /// Used to dispatch system events, by raising an event that the
    /// controller class subscribes to every time any system event is
    /// raised.
    /// Client code has no need to use this class. (internal class)
    /// </summary>
    internal class CairngormEventDispatcher
    {
        private static CairngormEventDispatcher instance;

        /// <summary>
        /// Returns the single instance of the dispatcher
        /// </summary>
        /// <returns>single instance of the dispatcher</returns>
        public static CairngormEventDispatcher getInstance()
        {
            if ( instance == null )
                instance = new CairngormEventDispatcher();

            return instance;
        }

        /// <summary>
        /// private constructor
        /// </summary>
        private CairngormEventDispatcher()
        {
        }

        //...Other code omitted to focus on singleton
     }
}

In a multi-threaded application, different threads could run at line if ( instance == null ) and evaluate it to be true, then multiple instances could be created in getInstance(); the direct impact would be some CairngormEvent is dispatched, but the corresponding command is not executed. Here is the thread-safe while not-using-lock implementation:

//Thread-safe singleton implementation that not using lock
namespace SilverlightCairngorm.Control
{
    /// <summary>
    /// Used to dispatch system events, by raising an event that the
    /// controller class subscribes to every time any system event is
    /// raised.
    /// Client code has no need to use this class. (internal class)
    /// </summary>
    internal class CairngormEventDispatcher
    {
        private static readonly CairngormEventDispatcher _instance =
                       new CairngormEventDispatcher();

        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static CairngormEventDispatcher()
        {
        }

        /// <summary>
        /// private constructor
        /// </summary>
        private CairngormEventDispatcher()
        {
        }

        /// Returns the single instance of the dispatcher
        public static CairngormEventDispatcher
               Instance { get { return _instance; } }

        //...other code omitted to focus on singleton
    }
}

The static constructor is to help the C# compiler to ensure laziness of the type initializer to create the private static field of _instance correctly and only once; static constructors in C# are specified to execute only when the class is instantiated or a static member is referenced, and to execute only once per AppDomain.

Usually, in a Silverlight Cairngorm application, like in the demo project, the application's ModelLocator and FrontController are derived from the base abstract class, and are implemented as singletons. The above simple approach can be easily applied to those derived classes in the application. All the code changes for those two singletons are included in the demo project download.

Initializing an Event Delegate with an Empty Anonymous Method

Again, in the internal class CairngormEventDispatcher, the EventDispatched event is implemented in a way that requires the dispatchEvent method to check whether the delegate is null (to see if there is any event listener) before invoking it; here is the previous code:

//previous null event delegate checking that has potential race condition
/// <summary>
/// The subscriber to a system event must accept as argument
/// the CairngormEvent raised (within a CairngormEventArgs object)
/// </summary>
public delegate void EventDispatchDelegate(object sender,
                     CairngormEventArgs args);
/// <summary>
/// The single event raised whenever a Cairngorm system event occurs
/// </summary>
public event EventDispatchDelegate EventDispatched;

/// <summary>
/// dispatchEvent raises a normal .net event, containing the
/// instance of the CairngormEvent raised - to be handled by
/// the Controller Class
/// </summary>
public void dispatchEvent(CairngormEvent cairngormEvent)
{
  if (EventDispatched != null)
  {
      CairngormEventArgs args = new CairngormEventArgs(cairngormEvent);
      EventDispatched(null, args);
  }
}

Juval Lowy in his book Programming .NET Components, Second Edition offered a nice trick to hook up an empty anonymous method to the delegate in its declaration; then, we never have to check for null before invoking the delegate, since there will always be one no-op subscriber in its subscriber list. It can also avoid a potential race condition when un-subscribing in a multi-threaded application. Here is the updated code for EventDispatchDelegate:

// Leveraging empty anonymous methods for event delegate
/// <summary>
/// The subscriber to a system event must accept as argument
/// the CairngormEvent raised (within a CairngormEventArgs object)
/// </summary>

/// <param name=""""args"""" />a CairngormEventArgs object,
/// containing the raised event object
public delegate void EventDispatchDelegate(object sender, CairngormEventArgs args);
/// <summary>
/// The single event raised whenever a Cairngorm system event occurs
/// </summary>
public event EventDispatchDelegate EventDispatched = delegate { };

/// <summary>
/// dispatchEvent raises a normal .net event, containing the
/// instance of the CairngormEvent raised - to be handled by
/// the Controller Class
/// </summary>
/// <param name=""""cairngormEvent"""" />the raised Cairngorm Event
public void dispatchEvent(CairngormEvent cairngormEvent)
{
  CairngormEventArgs args = new CairngormEventArgs(cairngormEvent);
  EventDispatched(null, args);
}

Similarly, the ModelLocator uses the same approach to make the code simpler and safer; details are in the source code.

Thread-safe Data Binding Events

The ModelLocator is meant to be the DataContext (you can certainly have other data as DataContext) to the application for data binding, that's why the abstract base class implements the INotifyPropertyChange interface. It works well (even for the update collection data as shown in the demo project, each search result updates a collection of FlickRPhoto objects) in a single threaded application. Since Silverlight has the same rule regarding updating the UI (needs to perform on the same thread that creates the UI element) as WPF or Windows Forms does, it's important to make the INotifyPropertyChange implementation to be thread-safe for a multi-threaded application, or a Model is updated from a different thread other than the UI thread, and the PropertyChanged event is raised in a non-UI thread, Silverlight will throw an UnauthorizedAccessException exception (WPF will throw an InvalidOperationException exception for the same reason).

The idea to make the PropertyChanged event thread-safe in Silverlight is to check the executing thread is the UI thread before invoking the event delegates; if it's on the same thread, invoke it directly; if not, dispatch it to the correct thread, then the UI (data binding) updates without an exception.

Here is the thread-safe version of the ModelLocator's INotifyPropertyChange implementation:

namespace SilverlightCairngorm.Model
{
/// <summary>
/// modestyZ: 2008.10: derived from BindableBase, ModelLocator can be 
/// initialized/instantiated before RootVisual loaded
/// Custom ModelLocator will derive from this abstract class.
///
/// Custom ModelLocator usually implements Singleton pattern,
/// please see the example of CairgormEventDispatcher about 
/// how to make the Singleton thread-safe
/// </summary>
public abstract class ModelLocator : BindableBase
{
      protected ModelLocator()
      {
      }
}

The body of the abstract ModelLocator is almost empty, it is the base type, BindableBase, makes sure that the derived ModelLocator type will have to implement the INotifyPropertyChange interface and also the implementation is thread-safe:

namespace SilverlightCairngorm
  {
  /// <summary>
  /// modestyZ: 2008.10
  /// Abstract class that makes INotifyPropertyChanged implementation thread-safe
  /// Any type that serves as DataContext for data binding in a 
  /// multi-threaded application should derive from this type
  /// </summary>
  public abstract class BindableBase : INotifyPropertyChanged
  {
  //the dispatcher to ensure the PropertyChange event will raised for 
  //UI thread to update bound data
  protected Dispatcher currentDispatcher;
 public BindableBase() : this(null) { }
 public BindableBase(Dispatcher uiDispatcher)
  {
  currentDispatcher = uiDispatcher;
  }
 #region INotifyPropertyChanged Members
 public event PropertyChangedEventHandler PropertyChanged = delegate { };
  protected void NotifyPropertyChanged(string propertyName)
  {
  //the dispatcher initialization is deferred till the 
  //first PropertyChanged event raised
  CheckDispatcher();

  //check if we are on the Dispatcher thread if not switch
  if (currentDispatcher.CheckAccess())
  PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  else
  currentDispatcher.BeginInvoke(new Action<string>
			(NotifyPropertyChanged), propertyName);
  }
 #endregion
  /// <summary>
  /// check and initialize the UI thread dispatcher when 
  /// the constructor passed in null dispatcher instance
  /// </summary>
  protected void CheckDispatcher()
  {
  if (null != currentDispatcher)
  return;
 if (Application.Current != null &&
  Application.Current.RootVisual != null &&
  Application.Current.RootVisual.Dispatcher != null)
  {
  currentDispatcher = Application.Current.RootVisual.Dispatcher;
  }
  else // can't get the Dispatcher, throw an exception
  {
  throw new InvalidOperationException
	("CheckDispatcher must be invoked after that the RootVisual has been loaded");
  }
  }
  }
  }

Another reason to separate out the BindableBase type is to make it reusable for other potential model data types. For example, if the application has a view needs to data bind to its own View Model, rather than the ModelLocator, the View Model type can derive from BindableBase, it would get the same benefits of thread-safety from BindableBase.

The initialization of the UI thread's dispatcher is deferred till the very first NotitifyPropertyChange event raised, the protected CheckDispatcher method will retrieve the dispatcher from RootVisual to make sure it's actually referencing the UI thread's dispatcher. This deferred instantiation enables the ModelLocator can be instantiated before RootVisual is loaded.

Thread-safe ObservableCollection Wrapper

When dealing with dynamic data collection as DataContext for data binding in a multi-threaded application, we also need to make the INotifyCollectionChanged event to be dispatched on the UI thread as well. In Silverlight Cairngorm v0.0.1.3, the new type, BindableCollection, wraps ObservableCollection and derives from BindableBase to make both the INotifyCollectionChange and INotifyPropertyChange implementation thread-safe:

namespace SilverlightCairngorm
  {
/// <summary>
/// modestyZ@hotmail.com: 2008.10
/// Wrapper class that makes ObservableCollection thread-safe
/// Any ObservableCollection that serves as DataContext for 
/// data binding in multi-threaded application should be wrapped by this type
/// </summary>
/// <typeparam name="T"></typeparam>
public class BindableCollection<T> : BindableBase, INotifyCollectionChanged,
IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
{
private ObservableCollection<T> list;
public BindableCollection(ObservableCollection<T> list) : this(list, null) { }
 public BindableCollection
	(ObservableCollection<T> list, Dispatcher dispatcher) : base(dispatcher)
  {
  if (list == null)
  {
  throw new ArgumentNullException
	("The list must not be null for BindableCollection constructor");
  }
 this.list = list;
 INotifyCollectionChanged collectionChanged = list as INotifyCollectionChanged;
  collectionChanged.CollectionChanged += 
	delegate(Object sender, NotifyCollectionChangedEventArgs e)
  {
  NotifyCollectionChanged(this, e);
  };
 INotifyPropertyChanged propertyChanged = list as INotifyPropertyChanged;
  propertyChanged.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e)
  {
  NotifyPropertyChanged(e.PropertyName);
  };
  }

 #region INotifyCollectionChanged Members
 public event NotifyCollectionChangedEventHandler CollectionChanged = delegate { };
  protected void NotifyCollectionChanged
		(object sender, NotifyCollectionChangedEventArgs e)
  {
  //the dispatcher initialization could be deferred till the 
  //first PropertyChanged event raised
  CheckDispatcher();
  //check if we are on the Dispatcher thread if not switch
  if (currentDispatcher.CheckAccess())
  CollectionChanged(this, e);
  else
  currentDispatcher.BeginInvoke
	(new NotifyCollectionChangedEventHandler(CollectionChanged), this, e);
  }
}

In BindableCollection, while making data binding events thread-safe, it also implements all public methods that ObservableCollection exposes via 8 interfaces. All implementation details can be found at the downloadable source code.

Test Out Thread-safety

All of the above are the changes in the Silverlight Cairngorm to make it thread-safe; now, let's update our demo project to test it out. The major change in the demo project is in the SearchPhotoCommand's onResult method. The new ProcessResultFromThreadPool method will parse the FlickR API returning XML, transform it to a collection of FlickRPhoto objects, then notify data binding to populate new data; all this happens in a thread-pool thread (non-UI thread). Here is the code:

//Thread-safe testing in Command
namespace SilverlightCairngormDemo.Command
{
    public class SearchPhotoCommand : ICommand, IResponder
    {
        private SilverPhotoModel model = SilverPhotoModel.Instance;

        #region ICommand Members

        public void execute(CairngormEvent cairngormEvent)
        {
            //disable the "go" button
            model.ReadySearchAgain = false;

            //get search term from model
            string toSearch = model.SearchTerm;

            //begin talk to web service
            SearchPhotoDelegate cgDelegate =
                               new SearchPhotoDelegate(this);
            cgDelegate.SendRequest(toSearch);
        }

        #endregion

        #region IResponder Members

        public void onResult(object result)
        {
            //enable the "go" button
            model.ReadySearchAgain = true;

            model.SelectedIdx = -1;

            string resultStr = (string)result;
            if (String.IsNullOrEmpty(resultStr))
            {
                onFault("Error! (Server returns empty string)");
                return;
            }

            //ProcessResult(resultStr);
            ProcessResultFromThreadPool(resultStr);
        }

        /// <summary>
        /// could be executed in UI thread when invoked directly from onResult
        /// or will run from a threadpool thread when called by
        /// ProcessResultFromThreadPool
        /// </summary>
        /// <param name=""""resultStr"""" />
        private void ProcessResult(string resultStr)
        {
            XDocument xmlPhotos = XDocument.Parse(resultStr);
            if ((null == xmlPhotos) ||
                xmlPhotos.Element("rsp").Attribute(
                                  "stat").Value == "fail")
            {
                onFault("Error! (" + resultStr + ")");
                return;
            }

            //update the photoList data in model
            model.PhotoList =
                  xmlPhotos.Element("rsp").Element(
                  "photos").Descendants().Select(p => new FlickRPhoto
            {
                Id = (string)p.Attribute("id"),
                Owner = (string)p.Attribute("owner"),
                Secret = (string)p.Attribute("secret"),
                Server = (string)p.Attribute("server"),
                Farm = (string)p.Attribute("farm"),
                Title = (string)p.Attribute("title"),
            }).ToList<flickrphoto>();

            if (model.PhotoList.Count > 0)
                model.SelectedIdx = 0; //display the 1st image
            else
                onFault("No such image, please search again.");
        }

        public void onFault(string errorMessage)
        {
            //enable the "go" button
            model.ReadySearchAgain = true;

            //display the error message in PhotoList
            model.SelectedIdx = -1;
            model.PhotoList = new List<flickrphoto>() { new FlickRPhoto() {
                               Title = errorMessage } };
        }

        private void ProcessResultFromThreadPool(string resultStr)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(NonUIThreadWork),
                                resultStr);
        }

        private void NonUIThreadWork(object resultObj)
        {
            ProcessResult((string)resultObj);
        }

        #endregion
    }
}

You can put a break point at the line currentDispatcher.BeginInvoke(new Action <string>(NotifyPropertyChanged), propertyName); in the ModelLocator base class to see how it works. Then, comment out ProcessResultFromThreadPool(resultStr); from onResult, and un-comment out ProcessResult(resultStr); (to make it run in the same thread) to see how it behaves.

History

  • 2008.09.14 - First post based on Silverlight Cairngorm
  • 2008.10.05 - Silverlight Cairngorm v.0.0.1.2 (Download source and demo project update)
    • Updated Silverlight Cairngorm FrontController ---- each registered Cairngorm Event will be handled by a new instance of corresponding Cairngorm Command, this will make Silverlight Cairngorm FrontController work in the same way as Flex's Cairngorm 2.2.1's FrontController.
    • Also updated demo project to reflect the new addCommand signature to pass in the type of Command, rather than the instance of Command.
    • Demo project is also updated to use Silverlight 2 RC0.
  • 2008.10.18 - Silverlight Cairngorm v.0.0.1.3 (Download source and demo project update)
    • Added thread-safe BindableBase and BindableCollection
    • Changed ModelLocator to derive from BindableBase
    • Verified all source code works with Silverlight 2 RTW

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here