Monday, February 2, 2009

Responsive WPF User Interfaces Part 4

Multi-threaded Programming in WPF

In the last post (Part 3) of the series we found a way to get our UI responsive and provide some visual clue to the user that work was being performed. However we found that there were limitations to the model proposed as only small/fast pieces of work could be performed. Once a task started taking a longer time to process, the User Interface (UI) became unresponsive again. This was because the task were still being performed on the UI thread via the dispatcher. This post looks at how we can leverage the multi-threaded functionality in the .NET framework to perform tasks that have a long or unknown duration while keeping the UI responsive.
The .NET framework offers several ways to invoke work on a separate thread including:
  • The Start method on a System.Threading.Thread object
  • BeginInvoke Method on a delegate
  • QueueUserWork method on the System.Threading.ThreadPool type
  • The RunWorkerAsync method on a System.ComponentModel.BackgroundWorker
The merits of each of these is beyond the scope of this post. I am going to pick one and run with it. Ideally I will discuss the merits in another post.

A quick code refactor*

I think it is fair to point out at the moment the adding concurrency handling to our code behind on the Window is going to balloon out the code. This concerns me as I think we already have a problem with single responsibility. The window code is clearly at the presentation layer. It has some properties relevant to the Presentation layer and then has some very specific code regarding the File System. I would like to move that code out in to a model. Doing so will realise several benefits:
  • The code will have a single responsibility (to serve as a presentation model)
  • I should be come testable
  • Ideally the implementation details could become pluggable (swap file system for SkyDrive, flickr, Database etc for example). However this is a benefit, not a goal of the changes we are about to make.
So in this next piece of code I will take the key parts that I want to keep in the model
  • the Images property (ObservableCollection<String>)
  • the IsLoading property (bool)
  • the SourcePath property (string)
  • an implementation of INotifyPropertyChanged interface
  • a new SetSourcePathCommand property. I prefer Commands to event handlers. Using a command will allow us to stop the user changing the Source while we are processing.
At a glance the new code will look like this:
public class PhotoAlbumModel : INotifyPropertyChanged
{
  #region Fields ...

  public ObservableCollection<string> Images {...}
  public bool IsLoading {...}
  public string SourcePath {...}

  public ICommand SetSourcePathCommand { get { return setSourcePathCommand; } }

  #region INotifyPropertyChanged Members...
}

For this example I will use a custom implementation of ICommand that allows me to specify the delegates for the CanExecute and Execute methods. This implementation is called a DelegateCommand. There is an implementation of this in the Composite Application Guidance that I would recommend you use in your code. So I update my Model to use a delegate command and add two methods to handle the CanExecute and Execute behaviour.
private readonly DelegateCommand setSourcePathCommand;
public PhotoAlbumModel()
{
  setSourcePathCommand = new DelegateCommand(ExecuteSetSourcePath, CanSetSourcePath);
  this.PropertyChanged += (sender, e) => { if (e.PropertyName == "IsLoading") setSourcePathCommand.OnCanExecuteChanged(); };
}
#region Command handlers
void CanSetSourcePath(object sender, CanExecuteEventArgs e)
{
  e.CanExecute = !IsLoading;
}

void ExecuteSetSourcePath(object sender, ExecutedEventArgs e)
{
  System.Windows.Forms.FolderBrowserDialog openFolderDlg = new System.Windows.Forms.FolderBrowserDialog();
  openFolderDlg.RootFolder = Environment.SpecialFolder.Desktop;
  if (openFolderDlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
  {
    if (!string.IsNullOrEmpty(openFolderDlg.SelectedPath))
    {
      SourcePath = openFolderDlg.SelectedPath;
      StartLoadingFiles(SourcePath);
    }
  }
}
#endregion

Bear with me during this reshaping of our code. I will get back to Responsive UIs, but I believe this is important. In our implementation of the Execute handler we call a method we have yet to create StartLoadingFiles. This is where our threading code will be isolated to. I think this makes our code much easier to understand, as so far the class is comprised of simple concepts like Properties, Notification Events & Commands. In our new StartLoadingFiles method we introduce our first Multi-threaded code by implementing a BackgroundWorker.
#region Private method
void StartLoadingFiles(string path)
{
  IsLoading = true;
  BackgroundWorker fileWorker = new BackgroundWorker();
  fileWorker.DoWork += (sender, e) =>
  {
    LoadPath(path);
  };
  fileWorker.RunWorkerCompleted += (sender, e) =>
  {
    IsLoading = false;
  };
  fileWorker.RunWorkerAsync();
}

void LoadPath(string path)
{
  IEnumerable<string> files = fileService.ListFiles(path, IsImage);
  foreach (string item in files)
  {
    dispatcher.Invoke(new Action(() => { Images.Add(item); }));
  }

  IEnumerable<string> directories = fileService.ListDirectories(path);
  foreach (string item in directories)
  {
    LoadPath(item);
  }
}

bool IsImage(string file){...} //Same implementation as before.
#endregion

I hope you agree that so far the code is fairly simple to read and understand Ed-yes it still could be better, no it was not driven out by tests. We have introduced 3 methods here. One is our old friend IsImage that we stole from previous code. StartLoadingFiles method is our entry point to running the Multi-threaded code, and the LoadPath method is the code that will be executed from the BackgroundThread. Four things I would like to point out at this point:
  1. The implementation of the BackgroundWorker is just one example of how you could kick off parallel execution. Don't take this method as best practice.
  2. Note the use of dispatcher.Invoke in the for loop of the LoadPath method. We cant use BeginInvoke in a loop as the variable [item] is passed by ref. If we did, we would be setting the value of a reference type, passing the reference type to a the non-blocking call and then changing the value of the reference type by moving to the next value in the enumeration. This would result in most of the values queued on the dispatcher to just the value of the last item in the enumeration.
  3. We have access to a private variable called dispatcher. Where did that come from? Will cover that in a moment.
  4. We are calling ListFiles and ListDirectories methods on a variable called fileService. Operations on the file system have been delegated to another class now.

To actually make the new code work we have to introduce some new fields and create a constructor that enables these fields to be set.

#region Fields
private readonly Dispatcher dispatcher;
private readonly IFileService fileService;
private readonly DelegateCommand setSourcePathCommand;
private readonly ObservableCollection<string> images = new ObservableCollection<string>();
private string sourcePath;
private bool isLoading = false;
#endregion

public PhotoAlbumModel(Dispatcher dispatcher, IFileService fileService)
{
  this.dispatcher = dispatcher;
  this.fileService = fileService;
  setSourcePathCommand = new DelegateCommand(ExecuteSetSourcePath, CanSetSourcePath);
  this.PropertyChanged += (sender, e) => { if (e.PropertyName == "IsLoading") setSourcePathCommand.OnCanExecuteChanged(); };
}


This also indicates that we have a new Interface of IFileService and lets give you an implementation of that interface FileSystemService.

public interface IFileService
{
  IEnumerable<string> ListFiles(string path, Predicate<String> filter);
  IEnumerable<string> ListDirectories(string path);
}
public class FileSystemService : IFileService
{
  public IEnumerable<string> ListFiles(string path, Predicate<string> filter)
  {
    Collection<string> images = new Collection<string>();
    try
    {
      string[] files = System.IO.Directory.GetFiles(path);
      foreach (string file in files)
      {
        if (filter(file))
          images.Add(file);
      }
    }
    catch (UnauthorizedAccessException) { } //Swallow
    return images;
  }

  public IEnumerable<string> ListDirectories(string path)
  {
    try
    {
      return System.IO.Directory.GetDirectories(path);
    }
    catch (UnauthorizedAccessException)
    {
      return new List<string>().ToArray();
    }
  }
}

I think that is a nice piece of code now Ed-still could be better. Classes are fairly cohesive and their intent I think is fairly.

Presentation

Now how do we display this "Model"? Assuming the reader is an Intermediate WPF developer, they will know what a DataTemplate is. For the example I will create a window and add a DataTemplate that looks very similar to the XAML from previous versions of the code.

<Window x:Class="ArtemisWest.Demo.ResponsiveUI._4_MultiThreaded.WindowHost"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ArtemisWest.Demo.ResponsiveUI._4_MultiThreaded"
        Title="WindowHost" Height="300" Width="300">
  <Window.Resources>
    <DataTemplate DataType="{x:Type local:PhotoAlbumModel}">
      <DataTemplate.Resources>
        <BooleanToVisibilityConverter x:Key="boolToVisConverter"/>
      </DataTemplate.Resources>
      <DockPanel>
        <DockPanel DockPanel.Dock="Top">
          <ProgressBar IsIndeterminate="True" Height="18" DockPanel.Dock="Top" Visibility="{Binding Path=IsLoading, Converter={StaticResource boolToVisConverter}}" />
          <Button x:Name="SetSourcePath" Command="{Binding Path=SetSourcePathCommand}" DockPanel.Dock="Right">Set Source</Button>
          <Border BorderThickness="1" BorderBrush="LightBlue" Margin="3">
            <Grid>
              <TextBlock Text="{Binding SourcePath}">
        <TextBlock.Style>
          <Style TargetType="TextBlock">
            <Setter Property="Visibility" Value="Visible"/>
            <Style.Triggers>
              <DataTrigger Binding="{Binding SourcePath}" Value="">
                <Setter Property="Visibility" Value="Collapsed"/>
                </DataTrigger>
              <DataTrigger Binding="{Binding SourcePath}" Value="{x:Null}">
                <Setter Property="Visibility" Value="Collapsed"/>
              </DataTrigger>
            </Style.Triggers>
          </Style>
        </TextBlock.Style>
              </TextBlock>
              <TextBlock Text="Source not set" Foreground="LightGray" FontStyle="Italic">
        <TextBlock.Style>
          <Style TargetType="TextBlock">
            <Setter Property="Visibility" Value="Collapsed"/>
            <Style.Triggers>
              <DataTrigger Binding="{Binding SourcePath}" Value="">
                <Setter Property="Visibility" Value="Visible"/>
                </DataTrigger>
              <DataTrigger Binding="{Binding SourcePath}" Value="{x:Null}">
                <Setter Property="Visibility" Value="Visible"/>
              </DataTrigger>
            </Style.Triggers>
          </Style>
        </TextBlock.Style>
              </TextBlock>
            </Grid>
          </Border>
        </DockPanel>
        <ListView ItemsSource="{Binding Images}" ScrollViewer.VerticalScrollBarVisibility="Visible" />
      </DockPanel>
    </DataTemplate>
  </Window.Resources>
</Window>

The only real change we have done here is the introduction of a DataTemplate and the use of a Command instead of the Click event handler.

Testing responsiveness

If we run this code we again get a similar feel to the responsive UI we produced in the last post. We were able to make the code from the previous post less responsive by throwing in some Thread.Sleep method calls. The idea here was to replicate some sort of IO delay (disk, network, database etc). To even the playing field lets do the same to our new FileSystemService class.

public IEnumerable<string> ListFiles(string path, Predicate<string> filter)
{
  System.Threading.Thread.Sleep(1000);
 
public IEnumerable<string> ListDirectories(string path)
{
  System.Threading.Thread.Sleep(1000);

Running this code now shows a marked improvement on our previous example. While adding the sleep method makes the code painfully slow to run, the UI is unaffected and happily will respond to interaction without becoming jittery or showing other signs of UI lag.
I do apologise for the rather wide berth we took on this post. This is showcases when one strategy will take you so far but a step back is needed to allow for progress with a better strategy.

*It is not really refactoring but as my friend Grae calls it refuctoring (ie refactoring with out tests as our safety net. We cross our fingers and hope we didn't break anything).

Considering that we have made some changes that are intended to allow for testable code, and that by our own admission we were refuctoring not refactoring I think the next obvious path to take is to see if we can drive out a similar model to what we have through tests. This will also force us to tackle some unknowns about unit testing when the dispatcher is involved.

Next - Creating tests for our multi-threaded WPF code in Part 5

Previous - "User feedback and the limitations of the Dispatcher" in Part 3

Back to series Table Of Contents

Working version of the code can be found here

No comments: