Property change notifications for multithreaded Silverlight applications
April 2, 2010
As I’ve been developing more complex Silverlight business applications, I’ve been increasingly relying on BackgroundWorker to offload complex calculations and operations to the background thread.
Something I didn’t have to worry about in the typical user interface thread-only implementation of my app was which thread change notifications fire on.
Here’s a typical scenario:
- You have a CLR property on a data/model object in your app. In a background thread, the property is updated.
- As all data and model objects should be, the type implements INotifyPropertyChanged (the data binding system relies on this to know when to update bindings).
- The property changed event is fired, and listeners react to the change, including the data binding system.
- The data binding system throws an invalid cross thread exception, since all UI operations, including handing off the binding changes, need to happen on the UI thread (but it’s happening on a background thread)
So what you really need to do is funnel those change notifications back to always happen on the user interface thread – that’s what makes the most sense given the data binding requirement.
By using a dispatcher, which can accept a BeginInvoke for that other thread, it will all just work and binding will move along happily:
This scenario is ripe for a few helper classes that I’m open to receiving feedback on. Hope you agree with my approach of using a ‘smart dispatcher’.
One Dispatcher to rule them all
The only thing you need to fire an Action on the primary UI thread is a reference to a Dispatcher instance. Unfortunately this isn’t something you can request from a background thread – you need to have the instance already stored away in most cases.
As a result, I’ve a static helper class and in it, I try and make sure that there is an instance stored before any of my data binding or model code is used.
Though I could have used SynchronizationContext instead, Dispatcher is a little easier to use, and better suited to WPF and Silverlight apps.
In my App.xaml.cs, I add a line that calls Initialize on my class. This is to make sure that there is always an available dispatcher for other threads to get access to:
public App() { this.Startup += this.Application_Startup; this.Exit += this.Application_Exit; this.UnhandledException += this.Application_UnhandledException; SmartDispatcher.Initialize(Deployment.Current.Dispatcher); InitializeComponent(); }
If you didn’t want to have to add this code to initialization, my implementation of a dispatcher helper class falls back to trying to grab the dispatcher from the root visual – but this won’t work in all scenarios, so I prefer the above. One more thing to remember however.
Being smart about using the dispatcher
It’s easy enough to replace all event firings for property changes to go through the dispatcher. However, this will reduce performance some; instead of the call being made immediately, it’s made at a later time.
There is a hidden method called CheckAccess on Dispatcher that returns true if you are on the same thread that the Dispatcher was first created in. By checking with it before making a call, we can make a better perf choice:
- If CheckAccess is true and we’re on the UI thread, just go ahead and directly invoke the Action.
- Else, BeginInvoke with the Dispatcher that will take care of getting it done on the UI thread later.
I’ve also done some optimization to try and ensure that the design-time experience at least stays consistent. In general you shouldn’t have background operations ever occurring in a design-time scenario.
The static helper class I’ve created is called SmartDispatcher and simplifies all of this logic.
So here’s my smart dispatcher implementation:
// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System.ComponentModel; namespace System.Windows.Threading { /// <summary> /// A smart dispatcher system for routing actions to the user interface /// thread. /// </summary> public static class SmartDispatcher { /// <summary> /// A single Dispatcher instance to marshall actions to the user /// interface thread. /// </summary> private static Dispatcher _instance; /// <summary> /// Backing field for a value indicating whether this is a design-time /// environment. /// </summary> private static bool? _designer; /// <summary> /// Requires an instance and attempts to find a Dispatcher if one has /// not yet been set. /// </summary> private static void RequireInstance() { if (_designer == null) { _designer = DesignerProperties.IsInDesignTool; } // Design-time is more of a no-op, won't be able to resolve the // dispatcher if it isn't already set in these situations. if (_designer == true) { return; } // Attempt to use the RootVisual of the plugin to retrieve a // dispatcher instance. This call will only succeed if the current // thread is the UI thread. try { _instance = Application.Current.RootVisual.Dispatcher; } catch (Exception e) { throw new InvalidOperationException("The first time SmartDispatcher is used must be from a user interface thread. Consider having the application call Initialize, with or without an instance.", e); } if (_instance == null) { throw new InvalidOperationException("Unable to find a suitable Dispatcher instance."); } } /// <summary> /// Initializes the SmartDispatcher system, attempting to use the /// RootVisual of the plugin to retrieve a Dispatcher instance. /// </summary> public static void Initialize() { if (_instance == null) { RequireInstance(); } } /// <summary> /// Initializes the SmartDispatcher system with the dispatcher /// instance. /// </summary> /// <param name="dispatcher">The dispatcher instance.</param> public static void Initialize(Dispatcher dispatcher) { if (dispatcher == null) { throw new ArgumentNullException("dispatcher"); } _instance = dispatcher; if (_designer == null) { _designer = DesignerProperties.IsInDesignTool; } } /// <summary> /// /// </summary> /// <returns></returns> public static bool CheckAccess() { if (_instance == null) { RequireInstance(); } return _instance.CheckAccess(); } /// <summary> /// Executes the specified delegate asynchronously on the user interface /// thread. If the current thread is the user interface thread, the /// dispatcher if not used and the operation happens immediately. /// </summary> /// <param name="a">A delegate to a method that takes no arguments and /// does not return a value, which is either pushed onto the Dispatcher /// event queue or immediately run, depending on the current thread.</param> public static void BeginInvoke(Action a) { if (_instance == null) { RequireInstance(); } // If the current thread is the user interface thread, skip the // dispatcher and directly invoke the Action. if (_instance.CheckAccess() || _designer == true) { a(); } else { _instance.BeginInvoke(a); } } } }
A property change base class
Next up, instead of having to manually wire up the INotifyPropertyChanged interface in all my data classes, I prefer to derive from a common base class, PropertyChangedBase, that has logic in it to use my smart dispatcher.
This base class:
- Has a protected NotifyPropertyChanged method that does all the real work
- Statically stores created event arguments to improve performance over time (only one PropertyChangedEventArgs instance is required per property name in the application’s lifetime)
- Uses SmartDispatcher to use a dispatcher only if not running on the user interface thread, otherwise directly invokes the property change handler.
// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; using System.Collections.Generic; using System.Windows.Threading; namespace System.ComponentModel { /// <summary> /// A base class for data objects that implement the property changed /// interface, offering data binding and change notifications. /// </summary> public class PropertyChangedBase : INotifyPropertyChanged { /// <summary> /// A static set of argument instances, one per property name. /// </summary> private static Dictionary<string, PropertyChangedEventArgs> _argumentInstances = new Dictionary<string, PropertyChangedEventArgs>(); /// <summary> /// The property changed event. /// </summary> public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// Notify any listeners that the property value has changed. /// </summary> /// <param name="propertyName">The property name.</param> protected void NotifyPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { PropertyChangedEventArgs args; if (!_argumentInstances.TryGetValue(propertyName, out args)) { args = new PropertyChangedEventArgs(propertyName); _argumentInstances[propertyName] = args; } // Fire the change event. The smart dispatcher will directly // invoke the handler if this change happened on the UI thread, // otherwise it is sent to the proper dispatcher. SmartDispatcher.BeginInvoke(delegate { handler(this, args); }); } } } }
Putting it all together
To use this in your own application, you take your model/data classes and derive from PropertyChangedBase. Then, in CLR setters, always call NotifyPropertyChanged:
public class SampleData : PropertyChangedBase { private string _someText; public string SomeText { get { return _someText; } set { _someText = value; NotifyPropertyChanged("SomeText"); } } }
Grab the code
I’ve decided to place these classes inside their appropriate system namespaces: System.Windows.Threading for the smart dispatcher, and System.ComponentModel for the PropertyChangedBase. Hope that isn’t too offensive.
Here are the files for download as well:
Hope this helps. Let me know what you think.
Updated 8/6/2014: Thanks to Mark Bishop at Microsoft for suggesting a fix; null/empty property names are accepted per msdn.