Andrew Whiddett on Asynchronous Databinding, the Dispatcher class and using DispatcherPriority.Background
Andrew Whiddett is a developer at REZN8, an amazing interactive design firm. REZN8 built the Netflix demo that was shown at PDC05. Andrew doesn't have a blog and asked me to be a proxy for a code sample he wrote, which I was more than obliged to do! He's a great developer and, until he gets his own blog, I'll be proxying any information he wants to share. The code sample itself is posted here and his mini-article is posted below:
*******************************************************************************************
The WPF samples are all very nice, but they typically are relatively simple implementations to show off a particular API set. We all know that real applications are more complex, and in most cases; getting data takes a period of time, which we need to account for in the application design.
The WPF binding system too so powerful to be ignored, in that it allows a complete separation of the UI Design from the business object implementation; this gives the application a level of abstraction that has never been available before, but must be implemented with real world issues; such as return data performance.
Here is an example that shows how to deal with some of these real world binding issues.
Let’s say there is a CLR based data bound object that you are trying to implement. For example a collection of FOO objects, e.g.
- An Object called FOO that contains a bunch of properties that we want to bind (in this case it just contains a description)
- A Collection of FOO objects; for example to populate a list box etc
- A Factory to get the reference to the Collection of FOO objects
Here is a starting point
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Configuration;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace SampleMTApp
{
public class FOO : INotifyPropertyChanged
{
#region Private Data
private string _description = "";
#endregion
#region Constructors
public FOO(string sDescription)
{
Description = sDescription;
}
#endregion
#region Property Handlers
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
#region Public Properties
public string Description
{
get
{
return _description;
}
set
{
_description = value;
NotifyPropertyChanged("Description");
}
}
#endregion
}
public class MyDataObjects : ObservableCollection<FOO>
{
public MyDataObjects()
{
FillTheData();
}
protected void FillTheData()
{
for (int iLoop = 0; iLoop < 1000; iLoop++)
{
this.Add(new FOO("First String"));
}
}
}
public class Factory
{
public MyDataObjects GetMyDataObjects
{
get
{
return new MyDataObjects();
}
}
}
}
Which we put into a test frame e.g.
<?Mapping XmlNamespace="SampleMTApp" ClrNamespace="SampleMTApp"?>
<Grid
xmlns="https://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005"
xmlns:c="https://schemas.microsoft.com/winfx/2005/06/markup-compatibility"
xmlns:d="https://schemas.microsoft.com/expression/interactivedesigner/2005"
c:Ignorable="d"
Background="#FFFFFFFF"
x:Name="DocumentRoot"
x:Class="SampleMTApp.Scene1"
Width="640" Height="480" xmlns:SampleMTApp="SampleMTApp">
<Grid.Resources>
<Storyboard x:Key="OnLoaded"/>
<ObjectDataProvider x:Key="FactoryDS" d:IsDataSource="True" ObjectType="{x:Type SampleMTApp:Factory}"/>
<DataTemplate x:Key="FOOTemplate1">
<StackPanel>
<TextBlock Text="{Binding Description}"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<Grid.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<EventTrigger.Actions>
<BeginStoryboard x:Name="OnLoaded_BeginStoryboard" Storyboard="{DynamicResource OnLoaded}"/>
</EventTrigger.Actions>
</EventTrigger>
</Grid.Triggers>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox Width="Auto" Height="Auto" MinWidth="0" MinHeight="0" Margin="232,183.6,208,96.4" x:Name="ListBox" ItemsSource="{Binding GetMyDataObjects, Mode=Default, Source={StaticResource FactoryDS}}" ItemTemplate="{DynamicResource FOOTemplate1}" RenderTransformOrigin="0.5,0.5"/>
</Grid>
No problem, right ?
Well, the initial load performance is not too great, but this follows the pattern implemented in most of the WPF examples..
Now add in a sleep : why you might ask; well really we are getting this data from somehere else; a database via a WCF call. In the real world, it might take some time, or even never make it at all.
So, the new fill routine looks something like:
protected void FillTheData()
{
for (int iLoop = 0; iLoop < 1000; iLoop++)
{
this.Add(new FOO("First String"));
Thread.Sleep(500);
}
}
What happens when you run this version ? The application takes FOREVER to load up.. Seriously, cancel the app; it will take nearly 8 minutes to display. Realy what we wanted to happen was for the data to flow in as it comes in over the wire rather than blocking the UI… Well that’s what threads are for after all…
So, lets kick off the FillTheData on a thread, that way we can stop blocking the UI threads…
In this case we can do this in the constructor, e.g.
public MyDataObjects()
{
Thread th = new Thread(new ThreadStart(this.FillTheData));
th.Start();
}
If you run it now, what happens is you get an exception:-
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
What this means is the collection has to be updated on the UI thread; not so nice; actually at this point I almost gave up trying to use the native data binding; but I really wanted to use it as it makes it so much easier for the UI designers to implement the design, and I really did not want to have to hand code it… well it took a bit of digging, but there is a solution: Kick the ObservableCollection.Add back to the UI (Dispatcher) thread…
So, in this example we create a method that populates the collection on a callback (the AddAFoo method), and we use BeginInvoke to Invoke a operation (to the callback) on the dispatcher thread.
// NOTE: This needs to run on the dispatcher thread...
protected object AddAFOO(Object data)
{
FOO myNewFoo = data as FOO;
if(myNewFoo != null) this.Add(myNewFoo);
return null;
}
protected void FillTheData()
{
for (int iLoop = 0; iLoop < 1000; iLoop++)
{
Application app = System.Windows.Application.Current;
if (app != null)
{
app.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(AddAFOO), new FOO("First String"));
Thread.Sleep(500);
}
}
}
}
So, run this version, and sure enough, the data trickles it goes; which really leaverages the power of the databind, without the UI designer having to worry about the implimentation at all.
Comments
- Anonymous
January 27, 2006
One little note here, if you use the Dispatcher associated with the app then you will only be able to use this solution in scenarios where the controls you are populating were created on the application's main thread (i.e. the one that called Run). It would be better maybe to use the Dispatcher associated with some specific Visual or to add a private Dispatcher variable to the MyDataObjects class that gets initialized to Dispatcher.CurrentDispatcher in its constructor. - Anonymous
January 28, 2006
Lonnie,
Thats a great point; I was trying to keep the example a little simple.. I am going to take this example further; Binding to current value was my next area.
I will include better Dispatcher handing in the next one after this.
Regards
Andrew
p.s. Thanks Karsten - Anonymous
February 09, 2006
This is to announce&nbsp;that Karsten has a blog post (authored by Andrew Whiddet) on the subject of... - Anonymous
February 11, 2006
The comment has been removed - Anonymous
February 18, 2006
Wow...this post saved me a lot of time and effort. Far simpler than not using databinding...which wasn't really an option for me. Thanks for the post. Any idea if databinding might learn how to hop threads in the future? Acquiring data on different threads seems pretty common. - Anonymous
March 26, 2006
Microsoft recently hosted a very interesting conference, MIX06,
in Las Vegas. By Microsoft's standards... - Anonymous
April 04, 2006
So MIX06 may be old news, but it is still fresh in my mind.&nbsp; I took a week off after the show but... - Anonymous
May 13, 2008
PingBack from http://mdavey.wordpress.com/2008/05/13/wpf-application-performance/