A quick sketch of asynchronous View Models

The How to make a loading graphic in WPF XAML? question on stackoverflow reminded me, that I wanted to write a short piece about the WPF infrastructure I’m currently writing. A first part was the Doing GUI architecture the Right Way where I describe the basic advantages of the View Model. Today I’m going to show some details of how this enables very smooth removal of background tasks from the UI Thread. Beware that this is work in progress, so edges might be rough. The basic structure is three-fold: the Model where the actual data resides, the View which interacts with the user and the View Model as a intermediate layer. I will start out with describing the View, which is really thin. Afterwards I’ll talk a bit why the Model alone is not enough. In the last part I’ll describe my approach to filling the gap.

The View

The easiest part is the View. In WPF, this is the easiest part:

<window ... DataContext="{StaticResource unitOfWork}">
<contentPresenter
Content="{Binding}"
ContentTemplateSelector="{StaticResource defaultTemplateSelector}" />
</window>

The unitOfWork is the root object of the current View Model tree and the defaultTemplateSelector uses meta data to map from a View Model class to the appropriate WPF template. In my case, this is coming from the bowels of the database but you can also just graft XAML (directly or by reference) onto the View Model. Everything else in the View is done by binding to parts of the View Model and choosing the right template, either by way of the default template selector or overriding it when a special context arises (e.g. list item vs. detail view).

The Model

The most basic component is the Business Layer or the Model. This layer may talk to the database, call out to services, or do whatever else is needed to implement its interface. There are many reasons why WPF cannot directly bind to this layer:
  • Not built for binding. No INotifyPropertyChanged or INotifyCollectionChanged interfaces.
  • May block. Talking to the network or the hard disk may introduce delays which are unacceptable for the UI.
  • Doesn’t know about multi-threading. Usually Models know only about Transactions, which are by themselves inherently single-threaded.
  • No place for the View’s state. Where should the View store things like window sizes, selected items or other ephemeral information?
None of these points is relevant when doing batch processing or when doing an ASP.NET site, which is a good benchmark for me that the Model shouldn’t burden itself with trying to solve them.

The View Model

To work around the “shortcomings” of the Model, this additional layer provides mechanisms to decouple user interaction from delays in the Model and a place to add additional properties to store and share View state between multiple Views. The basis of all presentable Models is this abstract base class:

public abstract class PresentableModel
: INotifyPropertyChanged
{
public ModelFactory Factory { get { return AppContext.Factory; } }
protected IContext DataContext { get; private set; }
protected IThreadManager UI { get { return AppContext.UiThread; } }
protected IThreadManager Async { get { return AppContext.AsyncThread; } }

public PresentableModel(IContext dataCtx) {
UI.Verify();
DataContext = dataCtx;
}

#region Public interface

private ModelState _State = ModelState.Loading;
public ModelState State {
get {
UI.Verify();
return _State;
}
protected set {
UI.Verify();
if (value != _State) {
_State = value;
OnPropertyChanged("State");
}
}
}

#endregion

#region INotifyPropertyChanged Members

private event PropertyChangedEventHandler _PropertyChangedEvent;
public event PropertyChangedEventHandler PropertyChanged {
add {
UI.Verify();
_PropertyChangedEvent += value;
}
remove {
UI.Verify();
_PropertyChangedEvent -= value;
}
}

protected virtual void OnPropertyChanged(string propertyName) {
UI.Verify();
if (_PropertyChangedEvent != null)
_PropertyChangedEvent(this, new PropertyChangedEventArgs(propertyName));
}

protected void AsyncOnPropertyChanged(string propertyName) {
Async.Verify();
UI.Queue(UI, () => OnPropertyChanged(propertyName));
}

#endregion

}


  • IThreadManager: a component that can Verify() whether execution is on the right thread and Queue() an action to the managed thread. Each model has two of them. The UI manager takes care of the UI thread and the Async manager handles asynchronous actions. The default implementations use the Dispatcher and the ThreadPool respectively.
  • State: a flag to indicate in which state the PresentableModel is. Possible states include Invalid, Loading, and Active. This also shows the default pattern for a Property on a PresentableModel: It can only be accessed from the UI thread (lines 19 and 23) and only notifies on real changes (line 24 and 26).
  • AsyncOnPropertyChanged(): the only method on PresentableModel that may be called from a background thread (line 54). By convention the method name starts with “Async”. The easiest of tasks, calling OnPropertyChanged() on the UI thread, is handled by using UI.Queue().

Actual Presentables

As an easy example of the flexibility of this model, I present you a SearchActionModel which can react on updates to the search term and to asynchronously batch queries to the database whose results are displayed incrementally.

public class SearchActionModel: PresentableModel
{
private string _searchTerm = null;
public string SearchTerm {
get {
UI.Verify();
return _searchTerm;
}
set {
UI.Verify();
if (_searchTerm != value) {
_searchTerm = value;
OnNotifyPropertyChanged("SearchTerm");
Async.Queue(DataContext, AsyncUpdateResults);
}
}

private ObservableCollection<searchResult> _results = new ObservableCollection<searchResult>();
private ReadOnlyObservableCollection<searchResult> _resultsProxy = new ReadOnlyObservableCollection<searchResult>(_results);
public ReadOnlyObservableCollection<searchResult> Results {
get {
UI.Verify();
return _resultsProxy;
}
}

private void AsyncUpdateResults() {
Async.Verify();
UI.Queue(UI, () => {
_results.Clear();
State = ModelState.Loading;
string searchTerm = _searchTerm;
Async.Queue(DataContext, () => {
var results = DataContext.GetQuery<searchResult>().Where(r => r.Name.Contains(searchTerm));
foreach(var r in results) {
UI.Queue(UI, () => _results.Add(r));
}
UI.Queue(UI, () => State = ModelState.Active);
});
});
}
}

Note how the multiple Queue() operations are nested to enforce an implicit and lock-less sequencing of the various actions. The View is quite primitive:

<userControl x:Class="App.View.SearchActionView" ...>
<dockPanel>
<textBox
Text="{Binding SearchTerm}"
DockPanel.Dock="Top" />
<ctrls:LoadingDindicator DockPanel.Dock="Top" />
<listBox
ItemsSource="{Binding Results}" />
</stackPanel>
</userControl>

Note: the LoadingIndicator is a small widget binding to the State Property and displaying a little spinning wheel when the Model is not Active.
Of course even this trivial example is not without its share of problems:
  • The Results collection is cleared on every input (line 30). A more sophisticated implementation might want to filter the results locally before going to the database again. Linq should make it easy to encapsulate the filter logic.
  • The loading code depends on a streaming Linq provider (line 35). If the underlying Linq provider blocks too long before returning the first result, a local workaround might be to implement client side paging and make multiple queries which only fetch a few elements each.
  • All Queue() magic aside, the code does play with fire and there are several possibilities to breach thread safety. For example, the search term has to be copied on the UI thread into a local variable (line 32) to avoid accessing the SearchTerm property from the background thread (line 34).

Conclusion and Future Work

This organisation of threading and binding is a powerful way to get highly flexible and easily bindable model classes for use with WPF. Still programming in a free threading application is playing with fire and even slight missteps may produce subtle and hard to find bugs. Other things are more of a nuisance because I didn’t come around implementing them yet. First, the State should support ref-counting semantics, because currently multiple parallel updates lead to the model being Active as soon as the first update finishes. The other things are related to the IThreadManager. It is currently impossible to abort actions once they’ve been queued and actions are mostly processed in FIFO which is annoying if somewhere is a longer running action, perhaps even consisting of multiple enqueued parts, which delay the reaction to the user’s last input.

dasZ.at

dasZ.at - the people behind ZBox.
dasZ.at Logo

Downloads

The lateset download will be available here - soon

Lastet blog

In our blog we talk about the latest developments around our tool ZBox.
>> Blog

Latest blog entries
>> Reference project: Implementing WordPress-based Website

Based on the reference design provided by Sabine herself, we implemented the zartbitter Website. The site is powered by the PHP-based WordPress blog and CMS engine, some additional plugins and a bit of yarn to hold it all together.


>> Setting a permanent search_path, the Right Way

Others recommend setting the search_path in the postgresql.conf. Current versions of PostgreSQL can set the search_path permanently on a per-database basis without having to touch system configuration files


>> Porting Puppet to Windows

In the course of the PuppetCamp Europe, I met with many people I only knew from IRC and email and it was a great time with interesting and inspiring discussions.

Today I want to write about the work I’m currently commisioned for by puppetlabs, porting puppet to Microsoft Windows.


>> Puppetcamp Europe 2010

I’m going to Puppetcamp Europe 2010!


>> Using gendarme with Code Contracts from .NET 4.0

When using gendarme on post-processed assemblies with code contracts and /throwonfailure set, a few things have to be ignored. Put the following lines into your ignore file (for example gendarmeignore.txt) and use it with –ignore on the command line.


>> Microsoft cannot decode Base64: News at 11!

Arthur has found a really nasty bug in Microsoft’s streaming Base64 decoder as used in the WCF: Connect Bug#541494


>> Kolab Connector binaries uploaded

Arthur moved on with programming and testing. Now we uploaded the first packages, which now contain the basic calendar and contacts synchronisation. The plugins already are able to synchronize our personal data.


>> Kolab Sync for Android and Outlook: Developer Preview

We are proud to announce the first developer preview for Kolab sync clients for both Android and Outlook. Both are licensed under the GPLv3.


>> Visual Studio 2008 Debugger

I didn’t know that: the VS2008 debugger has many bugs. Specifically, if you have a solution with multiple websites, debugging doesn’t work!

Symptom: Upon reaching a breakpoint, StepOver/Into do not work, but resume execution. This makes the debugger pretty pointless.


>> Building a simple MSBuild Task

On the “Using Studio’s “Custom Tool” in MSBuild” question, I was prompted to share the code. Here is a stripped down skeleton where I removed the actual calls to the custom tool. Since it is open source I didn’t really need to access the Visual Studio’s registry keys.