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
{
internal class CairngormEventDispatcher
{
private static CairngormEventDispatcher instance;
public static CairngormEventDispatcher getInstance()
{
if ( instance == null )
instance = new CairngormEventDispatcher();
return instance;
}
private CairngormEventDispatcher()
{
}
}
}
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:
namespace SilverlightCairngorm.Control
{
internal class CairngormEventDispatcher
{
private static readonly CairngormEventDispatcher _instance =
new CairngormEventDispatcher();
static CairngormEventDispatcher()
{
}
private CairngormEventDispatcher()
{
}
public static CairngormEventDispatcher
Instance { get { return _instance; } }
}
}
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:
public delegate void EventDispatchDelegate(object sender,
CairngormEventArgs args);
public event EventDispatchDelegate EventDispatched;
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
:
public delegate void EventDispatchDelegate(object sender, CairngormEventArgs args);
public event EventDispatchDelegate EventDispatched = delegate { };
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
{
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
{
public abstract class BindableBase : INotifyPropertyChanged
{
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)
{
CheckDispatcher();
if (currentDispatcher.CheckAccess())
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
else
currentDispatcher.BeginInvoke(new Action<string>
(NotifyPropertyChanged), propertyName);
}
#endregion
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
{
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
{
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)
{
CheckDispatcher();
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:
namespace SilverlightCairngormDemo.Command
{
public class SearchPhotoCommand : ICommand, IResponder
{
private SilverPhotoModel model = SilverPhotoModel.Instance;
#region ICommand Members
public void execute(CairngormEvent cairngormEvent)
{
model.ReadySearchAgain = false;
string toSearch = model.SearchTerm;
SearchPhotoDelegate cgDelegate =
new SearchPhotoDelegate(this);
cgDelegate.SendRequest(toSearch);
}
#endregion
#region IResponder Members
public void onResult(object result)
{
model.ReadySearchAgain = true;
model.SelectedIdx = -1;
string resultStr = (string)result;
if (String.IsNullOrEmpty(resultStr))
{
onFault("Error! (Server returns empty string)");
return;
}
ProcessResultFromThreadPool(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;
}
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;
else
onFault("No such image, please search again.");
}
public void onFault(string errorMessage)
{
model.ReadySearchAgain = true;
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