WPF threading model
While WPF is referred to as a Single Threaded model, WPF actually implements a model where by default two threads are used. One thread is used for rendering the display (Render thread), the other thread is used to manage the User Interface (UI Thread). The Render thread effectively runs in the background and you don't have to worry about it, while the UI thread receives input, handles events, paints the screen, and runs application code.The Dispatcher
The UI thread in WPF introduces a concept called a dispatcher. For a while it eluded me exactly what the dispatcher was and I had elevated it to the realm of magic. However I now like to think of it as a system made up of
- a worker,
- a set of prioritised queues
- and tasks that are allocated to the queues
The best example I can give of the dispatcher is my Cinema example as follows. When I go to my local cinema, they have 3 queues:
- "Normal"
- "Pre-booked Internet sales"
- and "Gold Class"
Obviously you can only queue up in the pre-booked queue if you have a receipt from the web site, and queuing in the "Gold Class" line means I will pay quite a lot more than the standard punters. This makes it easy for the staff to allocate their priorities to the queues. "Gold class" spend the most so get the first priority. "Internet sales" are the next priority as they have already paid. The normal queue is last priority. Now for a moment imagine if there was only one staff member manning the counter at the cinema. The staff member would deal with all of the Gold Class customers, and then all the Internet sales, and then the normal sales. If at any time more customers joined the Gold Class queue, the staff member would complete the current sale and then tend to the higher priority customer. This is in essence what the dispatcher does. The dispatcher can perform one task at a time and decides on which task to complete next based on the priority of the task.
UI threads must have a dispatcher and a dispatcher can belong to only one thread. All controls in WPF are eventually a sub-class of DispatcherObject.
Simplified view of the object hierarchy of a control:
- Object
- DispatcherObject
- DependencyObject
- Visual
- UIElement
- FrameworkElement
- Control
- FrameworkElement
- UIElement
- Visual
- DependencyObject
- DispatcherObject
Example - Asynchronous Single-threaded Programming
Now that we are familiar with the WPF threading model and Dispatchers we will start to look at some code and hypothesise as to the result of various coding styles. Let us start with a very simple example: a "prime number finder".
This is the basic XAML code that we will use.
<Window x:Class="ArtemisWest.Demo.ResponsiveUI._2_Dispatcher.Unresponsive" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="UnResponsive" Height="300" Width="300"> <Grid> <StackPanel Orientation="Horizontal" VerticalAlignment="Top"> <Label Margin="6" Padding="0" VerticalAlignment="Bottom" >Next prime number:</Label> <TextBlock Margin="6" Text="{Binding Path=PrimeNumber}" Width="100" VerticalAlignment="Bottom" /> <Button x:Name="StartStop" Click="StartStop_Click" Margin="6" VerticalAlignment="Bottom"> <Button.Style> <Style TargetType="Button" > <Setter Property="Content" Value="Start"/> <Style.Triggers> <DataTrigger Binding="{Binding Path=IsProcessing}" Value="True"> <Setter Property="Content" Value="Stop"/> </DataTrigger> </Style.Triggers> </Style> </Button.Style> </Button> </StackPanel> </Grid> </Window>
We define a Label, Textbox bound to our current PrimeNumber and a Button to start and stop processing.
The code-behind looks like this:
using System.ComponentModel; using System.Windows; namespace ArtemisWest.Demo.ResponsiveUI._2_Dispatcher { public partial class Unresponsive : Window, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private int primeNumber = 1; private bool isProcessing; public Unresponsive() { InitializeComponent(); this.DataContext = this; } public int PrimeNumber { get { return primeNumber; } set { primeNumber = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("PrimeNumber")); } } public bool IsProcessing { get { return isProcessing; } set { isProcessing = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsProcessing")); } } private void StartStop_Click(object sender, RoutedEventArgs e) { IsProcessing = !IsProcessing; int i = PrimeNumber; while (IsProcessing && i < 30000) { i++; if (IsPrime(i)) { PrimeNumber = i; } } } private bool IsPrime(int number) { bool result = true; for (int i = 2; i < number; i++) { if (number % i == 0) { result = false; break; } } return result; } } }
The important parts of the code here are the two methods near the end of the class; StartStop_Click and IsPrime. StartStop_Click is the event handler for the Button. It basically iterates from the value of the current prime number, up in increments of 1 looking for a new prime number. The IsPrime method does the work to figure out if the value is in fact a prime.
The while loop in the event handler will run until the IsProcessing is set to false or till it hits the upper limit I have set.
Can you spot the problem with this code?
Basically this code is all performed on the UI thread. Remember back to our Cinema Queue example where the sole staff member would deal with clients in a prioritised way. Also remember that the staff member always finished with the customer before re-evaluating whether there was a higher priority customer. Now consider the code we have here. When the user clicks the button, WPF will queue the event handler to be processed (probably after the render effects of a pressed button). Then the event handler leaps into the While loop updating the PrimeNumber property as it finds new values. Each of these updates to the PrimeNumber property fires a PropertyChanged event which in turns queues tasks for the UI to render the changes to the binding. But wait! While rendering should take a higher priority than the work we are doing, we have not signaled to the Dispatcher that we are done. So the Dispatcher is stuck dealing with our While Loop. Now what happens is we sit there processing prime numbers until we hit our upper limit. We don't even give the Dispatcher a chance to evaluate if we have clicked the button again to stop the process.
So how do we get around this? Well why don't we play nice and instead of being the customer that goes to the "Normal queue" and orders tickets, popcorn and soda for all of their friends and family, we could just order what we need, then rejoin the queue if we want to order some more tickets. Ed-Not such a good analogy. Well we know that we can queue work with the dispatcher and that would let it prioritise our work.
Let's consider the following changes to our C# code:
private readonly DispatcherPriority priority = DispatcherPriority.Normal;
//only required if you are using the old delegate method. If using Action<T> or lambdas this can be removed delegate void PrimeProcessDelegate(int number); private void StartStop_Click(object sender, RoutedEventArgs e) { IsProcessing = !IsProcessing; //Old way of invoking with a delegate PrimeProcessDelegate action = ProcessPrimesFrom; Dispatcher.BeginInvoke(action, priority, PrimeNumber); } private void ProcessPrimesFrom(int number) { if (IsPrime(number)) { PrimeNumber = number; } if (IsProcessing && number < 30000) { //Fancy new syntax for invoking a delegate Action<int> action = new Action<int>(ProcessPrimesFrom); Dispatcher.BeginInvoke(action, priority, number + 1); } } private bool IsPrime(int number) { bool result = true; for (int i = 2; i < number; i++) { if (number % i == 0) { result = false; break; } } return result; }
Here I still have the IsPrime method and the StartStop_Click event handler, but I have changed the code in the event handler and added a new method. Instead of using a While loop, I now ask the Dispatcher to queue a delegate for me. This delegate does my work and then recursively asks the Dispatcher to queue further calls to it. I have also explicitly specified the priority of the task. In our cinema example we had 3 queues, the Dispatcher has 12! The dispatcher has 10 levels of priority (12 if you include Invalid and Inactive) that can conceptually be thought of as priority queues. These priorities are defined by the enumeration DispatcherPriority.
Running the new version of the code you will see what I was hoping to produce in the first example. The start button kicks off the process, prime numbers are discovered, and the UI is refreshed automatically. I can also click the button to toggle processing. Yay!
SIDEBAR: Note that we have used the Dispatcher.BeginInvoke method for queuing our tasks. The Dispatcher also has a method called Invoke that takes the same argument parameters. What is the difference and can we use either? Well BeginInvoke is a non-blocking call and Invoke is a blocking call. This means that calling BeginInvoke essentially throws the task on the Dispatcher's queue and moves on. The Invoke method however places the task on the queue and then patiently waits until the task has been processed before moving on. If we were to substitute the BeginInvoke method with the Invoke method we would most probably get a StackOverflowException. So you can't just use either method you should use the right tool for the job. Later in the series we will use the Invoke method.
So that's it? That is all there is to Responsive UI's in WPF? No. We are about half way. :-(
The example we just worked through works well but will not scale. Remember that the dispatcher is bound to one thread. So all of our work is being done on one thread. Also remember that if any of the work took a while (like if IsPrime was called with an argument that was a prime number over 1,000,000) it would lock the UI. Also consider that the next computer you buy will probably have 4+ cores, so why are you only using 1 thread? And lastly consider that as we get better at WPF and more horsepower becomes available (CPUs + GPUs), customers will expect more animation, video, reflections etc... which all must be done on the UI thread. We don't want to bog down our poor old Dispatcher with calculating primes!
But this is good progress. You may find that using the Single Threaded approach we have discovered here, works well for you in some scenarios. These scenarios would be where tiny chunks of work can be done at a time and they are related to the UI/Presentation.
Next we discover how to provide user feedback and the limitations of the Dispatcher in Part 3.
Previous - Responsive WPF User Interfaces Part 1 - Introduction, Declarative Programming and Storyboards.
Back to series Table Of Contents
Working version of the code can be found here
6 comments:
Already had some comments about the use of Delegate and Action<T> around the office. The questions are; cant you just make the call
Dispatcher.BeginInvoke(ProcessPrimesFrom, PrimeNumber);?
"On my computer" I cant. ;-)
I little hack to have some nicer code could be to have some extension methods coded up like the below
public static class DispatcherObjectDelegates
{
public static void BeginInvoke<T>(this Dispatcher dispatcher, Action<T> method, T parameter)
{
dispatcher.BeginInvoke(method, DispatcherPriority.Background, parameter);
}
public static void BeginInvoke<T>(this Dispatcher dispatcher, Action<T> method, DispatcherPriority priority, T parameter)
{
dispatcher.BeginInvoke(method, priority, parameter);
}
public static void BeginInvoke<T1, T2>(this Dispatcher dispatcher, Action<T1, T2> method, T1 parameter1, T2 parameter2)
{
dispatcher.BeginInvoke(method, DispatcherPriority.Background, parameter1, parameter2);
}
public static void BeginInvoke<T1, T2>(this Dispatcher dispatcher, Action<T1, T2> method, DispatcherPriority priority, T1 parameter1, T2 parameter2)
{
dispatcher.BeginInvoke(method, priority, parameter1, parameter2);
}
}
this would allow to write code like:
Dispatcher.BeginInvoke<int>(ProcessPrimesFrom, PrimeNumber);
Hi Lee,
Nice metaphor!
//Fancy new syntax for invoking a delegate
Action_int_ action = new Action_int_(ProcessPrimesFrom);
Can actually be shortened to;
Action_int_ action = ProcessPrimesFrom;
C# 3.0 rocks...
This example did not produce the desired effect for me at all. The UI thread is still locked.
@Anon: Looking back at this post (2 1/2 yrs on) there are alsorts of ways to do concurrent work in WPF.
Having a quick look at the comment that proposes the extension method, I think you are right. This would still block the dispatcher. It is effectively performing a "trampoline" by asking the dispatcher to add the Delegate/Action to it's queue of work. If that delegate itself is a slow/blocking call then, yes, it will block the UI. So sorry about the mis-guidance.
Using Rx (or your own abstraction if you like) you would want to do the slow blocking work on another thread, and then pass the result back.
I would suggest reading further into the series to understand more about the dispatcher and becoming multi-threaded code in WPF.
Part 5 of the series is now defucnt with the advent of Rx, as it offers a far superior testing paradigm.
For more info on Rx check out the 9 part series here.
"DispatcherPriority.Normal;" should be changed to "DispatcherPriority.Background;" in this article.
.zip is correct.
Setting DispatcherPriority value to "DispatcherPriority.Background;" worked for me.
Post a Comment