2 April 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:
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’.
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.
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:
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); } } } }
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:
// (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); }); } } } }
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"); } } }
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.
Jeff Wilcox is a Software Engineer at Microsoft in the Open Source Programs Office (OSPO), helping Microsoft engineers use, contribute to and release open source at scale.