Monday, February 16, 2009

Responsive WPF User Interfaces Part 5

Testing multi-threaded WPF code

Stop press: Dont use this method. Read the article, but follow comment at bottom of post (ie Use Rx instead)

In the last post in this series we took a rather long way to get to the multi-threaded result we were looking for. This was because we decided to separate out our code in cohesive parts that allowed for more maintainable code. The popular method of creating maintainable code currently is through Unit Testing. An even better approach would be to apply Test Driven Development. TDD is a style of design where tests outlining our goals are written first and then code to satisfy those tests is created. Having identified the technical challenges of keeping a WPF application responsive, namely; the concepts related to the Dispatcher and parallel programming, we can now step back again to see the bigger picture as we did in part 4, to move forward.

In this post we will drive out our presentation model through tests and discover the issues related to testing WPF code and in particular testing the multi-threaded code. Just to set expectations, there is an assumption here that the reader is familiar with WPF Data Templates and unit testing with a mocking framework.

From my experience with unit testing it is always good to start off with a set of requirements that we could turn into tests. Continuing with our example of a thumbnail viewer, lets create a list of our requirements:

  • User can choose a folder
  • Can see what folder was selected
  • Will list all images in that directory and sub directories
  • Will indicate when it is processing
  • Cannot set folder while processing
  • The user interface will remain responsive during processing

For brevity, I will try to cheat as much as I can so more time can be spent on the technical issues of testing multi-threaded WPF code and less time spouting TDD goodness. I will first cheat by making the assumption that this is WPF code so will need implement constructs such as ICommand and INotifyPropertyChanged.

From these requirements I eventually came to the following test cases:

[TestClass]
class PhotoAlbumModelFixture
{
    [TestMethod]
    public void SetSourceCommand_sets_SourcePath(){}
    
    [TestMethod]
    public void SetSourceCommand_calls_FileService_with_value_from_FolderSelector(){}

    [TestMethod] 
    public void SetSourceCommand_calls_FileService_with_an_ImageOnly_filter() {}

    [TestMethod]
    public void SetSourceCommand_populates_images_from_FileService(){}

    [TestMethod]
    public void SetSourceCommand_recursively_calls_FileService(){}

    [TestMethod]
    public void SetSourceCommand_does_not_call_FileService_with_empty_value(){}

    [TestMethod]
    public void SetSourceCommand_sets_IsLoading_to_True(){}

    [TestMethod]
    public void SetSourceCommand_is_disabled_when_IsLoading(){}

    [TestMethod]
    public void FileService_is_called_Async(){}
}

When writing some of the tests I found that standard unit test code was fine. When I started to implement my code that had to make multithreaded calls, however, testing became problematic.

For example, my first attempt to write the "SetSourceCommand_calls_FileService_with_value_from_FolderSelector" test looked like this:

[TestMethod]
public void SetSourceCommand_calls_FileService_with_value_from_FolderSelector()
{
    Rhino.Mocks.MockRepository mockery = new Rhino.Mocks.MockRepository();
    ArtemisWest.Demo.ResponsiveUI._4_MultiThreaded.IFileService fileService = mockery.DynamicMock<ArtemisWest.Demo.ResponsiveUI._4_MultiThreaded.IFileService>();
    ArtemisWest.Demo.ResponsiveUI._5_MultiThreadedTest.IFolderSelector folderSelector = mockery.DynamicMock<ArtemisWest.Demo.ResponsiveUI._5_MultiThreadedTest.IFolderSelector>();

    PhotoAlbumModel model = new PhotoAlbumModel(Dispatcher.CurrentDispatcher, fileService, folderSelector);

    using (mockery.Record())
    {
        Expect.Call(folderSelector.SelectFolder()).Return(FolderPath);
        Expect.Call(fileService.ListFiles(FolderPath, null)).Return(EmptyList).IgnoreArguments().Constraints(
          Rhino.Mocks.Constraints.Is.Equal(FolderPath),
          Rhino.Mocks.Constraints.Is.Anything());
        Expect.Call(fileService.ListDirectories(FolderPath)).Return(EmptyList);
    }

    using (mockery.Playback())
    {
        model.SetSourcePathCommand.Execute(null);
    }
}

And following TDD rules of just write enough code to satisfy the test, my implementation of the Execute command handler looks like this:

void ExecuteSetSourcePath(object sender, ExecutedEventArgs e)
{
    SourcePath = this.folderSelector.SelectFolder();
    IsLoading = true;
    LoadPath(SourcePath);
    IsLoading = false;
}

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

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

Astute readers will notice the complete lack of "Responsive UI Code". Well I have yet to reach my test that specifies that the call to FileService should be asynchronous. So after I have finished writing all of my other tests and the code to support them, I write some test code to enforce the responsive requirement:

[TestMethod]
public void FileService_is_called_Async()
{
    SlowFileServiceFake fileService = new SlowFileServiceFake();
    Rhino.Mocks.MockRepository mockery = new Rhino.Mocks.MockRepository();
    ArtemisWest.Demo.ResponsiveUI._5_MultiThreadedTest.IFolderSelector folderSelector = mockery.DynamicMock<ArtemisWest.Demo.ResponsiveUI._5_MultiThreadedTest.IFolderSelector>();

    PhotoAlbumModel model = new PhotoAlbumModel(Dispatcher.CurrentDispatcher, fileService, folderSelector);

    using (mockery.Record())
    {
        Expect.Call(folderSelector.SelectFolder()).Return(FolderPath);
    }

    using (mockery.Playback())
    {
        model.SetSourcePathCommand.Execute(null);
        //Due to the sleeps in the stub this should only have 1 item at this stage.
        Assert.IsTrue(model.Images.Count < fileService.ExpectedFiles.Count);
    }
    Thread.Sleep(300);
    CollectionAssert.AreEquivalent(fileService.ExpectedFiles, model.Images);
}

private class SlowFileServiceFake : ArtemisWest.Demo.ResponsiveUI._4_MultiThreaded.IFileService
{
    public readonly List<string> ExpectedFiles = new List<string>() { "value0", "value1", "value2", "value3", "value4", "value5" };

    public IEnumerable<string> ListFiles(string path, Predicate<string> filter)
    {
        foreach (var item in ExpectedFiles)
        {
            yield return item;
            Thread.Sleep(30);
        }
    }

    public IEnumerable<string> ListDirectories(string path)
    {
        return EmptyList;
    }
}

Here I have used a Fake to provide canned results with a hard coded delay. In theory this should drip feed values to the presentation model.

I now go back to update my model to get the tests to pass. Following my own advice I change the code to look like the following:

void ExecuteSetSourcePath(object sender, ExecutedEventArgs e)
{
    string folder = this.folderSelector.SelectFolder();
    if (!string.IsNullOrEmpty(folder))
    {
        SourcePath = folder;
        StartLoadingFiles(SourcePath);
    }
}

void StartLoadingFiles(string path)
{
    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);
    }
}

To my surprise this code does not satisfy the test! The reason is because the Dispatcher is not in an Executing loop. This would normally be started by WPF when the application starts, but here we are just in test code. The solution lies with the DispatcherFrame. A dispatcher frame represents an execution loop in a Dispatcher. We can kick start this loop by simply instantiating a new DispatcherFrame and calling Dispatcher.PushFrame with it as the parameter. See Dan Crevier's blog for a really simple way to unit test in this fashion. If we implement Dan's method we end up with the following test code:

[TestMethod]
public void FileService_is_called_Async()
{
    SlowFileServiceFake fileService = new SlowFileServiceFake();
    Rhino.Mocks.MockRepository mockery = new Rhino.Mocks.MockRepository();
    ArtemisWest.Demo.ResponsiveUI._5_MultiThreadedTest.IFolderSelector folderSelector = mockery.DynamicMock<ArtemisWest.Demo.ResponsiveUI._5_MultiThreadedTest.IFolderSelector>();

    PhotoAlbumModel model = new PhotoAlbumModel(Dispatcher.CurrentDispatcher, fileService, folderSelector);

    using (mockery.Record())
    {
        Expect.Call(folderSelector.SelectFolder()).Return(FolderPath);
    }

    DispatcherFrame frame = new DispatcherFrame();
    model.PropertyChanged += (sender, e) => 
    {
        if (e.PropertyName == "IsLoading" && !model.IsLoading)
        {
            frame.Continue = false;
        }
    };

    using (mockery.Playback())
    {
        model.SetSourcePathCommand.Execute(null);
        //Due to the sleeps in the stub this should only have 1 item at this stage.
        Assert.IsTrue(model.Images.Count < fileService.ExpectedFiles.Count);
        Dispatcher.PushFrame(frame);
    }
    Thread.Sleep(300);
    CollectionAssert.AreEquivalent(fileService.ExpectedFiles, model.Images);
}

We could call it a day here, however I am not happy enough with this style of coding. My first problem is the explicit sleep I have in there. This makes my test slow. The fastest this test will ever run is 300ms. I could replace that with some sort of loop to check progress but then my test code is looking less like test code and more like low level code to dance-around dispatchers. This raises my other issue; the clutter I have added by adding the DispatcherFrame stuff.

My solution here was to create a construct that allowed me to code my intentions in a way that I thought to be clearer. I wanted to be able to

  • test code that would involve multi-threaded code and the dispatcher
  • specify what condition defined its completion
  • specify a timeout
  • treat the code block as a blocking call

The result I came up with is the following:

[TestMethod]
public void FileService_is_called_Async()
{
    Rhino.Mocks.MockRepository mockery = new Rhino.Mocks.MockRepository();
    SlowFileServiceFake fileService = new SlowFileServiceFake();
    ArtemisWest.Demo.ResponsiveUI._5_MultiThreadedTest.IFolderSelector folderSelector = mockery.DynamicMock<ArtemisWest.Demo.ResponsiveUI._5_MultiThreadedTest.IFolderSelector>();

    PhotoAlbumModel model = new PhotoAlbumModel(Dispatcher.CurrentDispatcher, fileService, folderSelector);
    using (mockery.Record())
    {
        Expect.Call(folderSelector.SelectFolder()).Return(FolderPath);
    }

    using (DispatcherTester dispatcherTester = new DispatcherTester(Dispatcher.CurrentDispatcher))
    {
        model.PropertyChanged += (sender, e) =>
        {
            if (e.PropertyName == "IsLoading" && !model.IsLoading)
                dispatcherTester.Complete();
        };
        dispatcherTester.Execute(() =>
          {
              model.SetSourcePathCommand.Execute(null);
              Assert.IsTrue(model.Images.Count < fileService.ExpectedFiles.Count);
          },
          new TimeSpan(0, 0, 2));
    }
    CollectionAssert.AreEquivalent(fileService.ExpectedFiles, model.Images);
}

This code describes a block of code that may require the use of the dispatcher, the condition where the asynchronous code is considered complete and a TimeSpan for a timeout. The test code is still not perfect but I think it is a step in the right direction.

The code for the DispatcherTester looks something like this:

internal sealed class DispatcherTester : IDisposable
{
  private readonly Dispatcher dispatcher;
  private readonly DispatcherFrame dispatcherFrame = new DispatcherFrame();

  public DispatcherTester(Dispatcher dispatcher)
  {
    this.dispatcher = dispatcher;
  }

  public Dispatcher Dispatcher
  {
    get { return dispatcher; }
  }

  public void Execute(Action action, TimeSpan timeout)
  {
    Execute(action, timeout, new TimeSpan(10));
  }

  public void Execute(Action action, TimeSpan timeout, TimeSpan wait)
  {
    Stopwatch stopwatch = Stopwatch.StartNew();
    action.Invoke();
    Dispatcher.PushFrame(dispatcherFrame);

    while (dispatcherFrame.Continue && stopwatch.Elapsed < timeout)
    {
      Thread.Sleep(wait);
    }
    if (stopwatch.Elapsed >= timeout)
    {
      dispatcherFrame.Continue = false;
      Dispatcher.DisableProcessing();
      Dispatcher.ExitAllFrames();
      throw new TimeoutException();
    }
  }

  public void Complete()
  {
    dispatcherFrame.Continue = false;
  }

  #region IDisposable Members

  public void Dispose()
  {
    dispatcherFrame.Continue = false;
  }

  #endregion
}

If any other developers have hit problems trying to write unit tests for their WPF code then I hope that this snippet of code helps. For those that are interested, the end result for the Model looks like

public class PhotoAlbumModel : INotifyPropertyChanged
{
  #region Fields
  private readonly Dispatcher dispatcher;
  private readonly _4_MultiThreaded.IFileService fileService;
  private readonly IFolderSelector folderSelector;
  private readonly DelegateCommand setSourcePathCommand;
  private readonly ObservableCollection<string> images = new ObservableCollection<string>();
  private string sourcePath;
  private bool isLoading = false;
  #endregion

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

  public ObservableCollection<string> Images
  {
    get { return images; }
  }

  public bool IsLoading
  {
    get { return isLoading; }
    private set
    {
      isLoading = value;
      OnPropertyChanged("IsLoading");
    }
  }

  public string SourcePath
  {
    get { return sourcePath; }
    private set
    {
      sourcePath = value;
      OnPropertyChanged("SourcePath");
    }
  }

  public ICommand SetSourcePathCommand { get { return setSourcePathCommand; } }

  #region Command handlers
  void CanSetSourcePath(object sender, CanExecuteEventArgs e)
  {
    e.CanExecute = !IsLoading;
  }

  void ExecuteSetSourcePath(object sender, ExecutedEventArgs e)
  {
    string folder = this.folderSelector.SelectFolder();
    if (!string.IsNullOrEmpty(folder))
    {
      SourcePath = folder;
      StartLoadingFiles(SourcePath);
    }
  }
  #endregion

  #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)
  {
    string extension = file.ToLower().Substring(file.Length - 4);
    switch (extension)
    {
      case ".bmp":
      case ".gif":
      case ".jpg":
      case ".png":
        return true;
      default:
        return false;
    }
  }
  #endregion

  #region INotifyPropertyChanged Members
  public event PropertyChangedEventHandler PropertyChanged;

  protected void OnPropertyChanged(string propertyName)
  {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
      handler(this, new PropertyChangedEventArgs(propertyName));
    }
  }
  #endregion
}

Notice that the Folder dialogue code from the previous post has now been pushed out to an interface which cleans up the code and also allows us to test this model.

The data template looks almost identical to the previous post's window xaml:

<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>

I hope that the examples here

  • give some insight on how you too can unit test your code,
  • show why a model is a better option for your WPF applications as you can test them
  • give you the courage to write multi threaded code in your WPF models in the knowledge that you can test it

Next we discover that sometimes controls can cause an unresponsive UI in Part 6.

Previous - Responsive WPF User Interfaces Part 4 - Multi-threaded Programming in WPF.

Back to series Table Of Contents

Working version of the code can be found here

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

Sunday, February 1, 2009

Responsive WPF User Interfaces Part 3

User feedback and the limitations of the Dispatcher

In the previous post in this series we found out how fairly innocent code can cause mayhem on our User Interface (UI), locking it up and leaving the user wondering what is going on. We discovered how the Dispatcher can help us out here by queuing small requests for it to do when it can. However, I claimed at the end of the session that the model wont scale well. Well here I will try to actually prove my claim and guide you through some fairly standard functionality that you may have to build in WPF.
Consider a simple Photo viewing application. The application is to load all images in a directory and it's subdirectories. The images are then to be displayed in some fashion. Pretty simple? Here is my first attempt at the presentation in XAML:

<Window x:Class="ArtemisWest.Demo.ResponsiveUI._3_SingleThreaded.SingleThreaded"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Single Threaded">
  <DockPanel>
    <DockPanel DockPanel.Dock="Top">
      <Button x:Name="SetSourcePath" Click="SetSourcePath_Click" 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>
</Window>

Fairly simple. I have

  • TextBlock that shows the Root path of where our images are being fetched from
  • Button to open the folder browser which sets the TextBlock value
  • ItemsControl to show the images that have been fetched (well the filenames of them at the moment)
  • Most of the code here is actually an attempt to display the text "Source not set" when the SourcePath is null or an empty string.

And here is the C# code to do the work:

public partial class SingleThreaded : Window, INotifyPropertyChanged
{
private string sourcePath;
private readonly ObservableCollection<string> images = new ObservableCollection<string>();

public SingleThreaded()
{
  InitializeComponent();
  this.PropertyChanged += SingleThreaded_PropertyChanged;
  this.DataContext = this;
}

public string SourcePath
{
  get { return sourcePath; }
  set
  {
    sourcePath = value;
    OnPropertyChanged("SourcePath");
  }
}

public ObservableCollection<string> Images
{
  get { return images; }
}

void SetSourcePath_Click(object sender, RoutedEventArgs 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;
    }
  }
}

void SingleThreaded_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
  if (e.PropertyName == "SourcePath")
  {
    //LoadImages(SourcePath); //Learning from previous experiance we use BeginInvoke instead.
    Dispatcher.BeginInvoke(new LoadImagesDelegate(LoadImages), DispatcherPriority.Background, SourcePath);
  }
}

delegate void LoadImagesDelegate(string folderLocation);
void LoadImages(string folderLocation)
{
  try
  {
    string[] files = System.IO.Directory.GetFiles(folderLocation);
    foreach (string file in files)
    {
      if (IsImage(file))
        Images.Add(file);
    }
  }
  catch (UnauthorizedAccessException) { } //Swallow

  try
  {
    string[] folders = System.IO.Directory.GetDirectories(folderLocation);
    foreach (string folder in folders)
    {
      //LoadImages(folder);//Learning from previous experiance we use BeginInvoke instead.
      Dispatcher.BeginInvoke(new LoadImagesDelegate(LoadImages), DispatcherPriority.Background, folder);
    }
  }
  catch (UnauthorizedAccessException) { } //Swallow
}

bool IsImage(string file)
{
  string extension = file.ToLower().Substring(file.Length - 4);
  switch (extension)
  {
    case ".bmp":
    case ".gif":
    case ".jpg":
    case ".png":
      return true;
    default:
      return false;
  }
}

#region INotifyPropertyChanged Members
/// <summary>
/// Implicit implementation of the INotifyPropertyChanged.PropertyChanged event.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Throws the <c>PropertyChanged</c> event.
/// </summary>
/// <param name="propertyName">The name of the property that was modified.</param>
protected void OnPropertyChanged(string propertyName)
{
  PropertyChangedEventHandler handler = PropertyChanged;
  if (handler != null)
    handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}

This is a little bit more complicated than previous examples. Let's start with the properties:

  • SourcePath is a string property with Notification enabled. This is what the TextBlock is bound to
  • Images is an ObservableCollection of strings. As we modify this collection the UI should update automatically.
Next, the event handlers:
  • SetSourcePath_Click handles the button click and shows an Open Folder dialog box. This allows the user to pick a folder to start looking for images in.
  • SingleThreaded_PropertyChanged handles all PropertyChanged events on itself. A bit silly but it will do for kicking off the loading of images when the SourcePath property changes
Finally the helper methods
  • LoadImages (with it's LoadImagesDelegate) does the grunt work for the class. It looks in a given directory for Images, adds them to the Images collection and then recursively calls itself with the Dispatcher.BeginInvoke
  • IsImage method is a simple method for identifying if a file is an image.

If you run this example the result is actually quite neat. Files start appearing as they are found and the UI feels responsive.

However, if I pick a folder with lots of stuff in it eg: the root of C:\, the process can take quite a while. What would be nice is if I could have some kind of visual clue that tells me when it is complete, or how much is left to go, or anything! Well lets look at our code. I think the simple option would be to have a boolean property call IsLoading that is simply set to true when we are loading images. When we are done set it back to false. So we drop this new piece of code into our C# which gives us the property we are looking for.

private bool isLoading = false;
public bool IsLoading
{
  get { return isLoading; }
  set
  {
    isLoading = value;
    OnPropertyChanged("IsLoading");
  }
}

I now want to modify my UI to show that something is loading. The easiest thing for me to do is throw a ProgressBar control in. So I put this code in just above my button at the top of the second DockPanel:

<Window ...>
  <Window.Resources>
    <BooleanToVisibilityConverter x:Key="boolToVisConverter"/>
  </Window.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" ...../>

Ok. Now back to our C# code. When the SourcePath changes I want to load the images. This seems like a good place to set our IsLoading property.

void Stackoverflow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
  if (e.PropertyName == "SourcePath")
  {
    IsLoading = true;
    Dispatcher.BeginInvoke(new LoadImagesDelegate(LoadImages), DispatcherPriority.Background, SourcePath);
    IsLoading = false;
  }
}

Hang on...when we call LoadImages we do it with a BeginInvoke method which is non-blocking. This effectively sets IsLoading to true, then back to false almost immediately. That's not right. Well lets change the BeginInvoke to Invoke. Nope that is not quite right either. Changing the initial BeginInvoke to a Invoke method call will just set the IsLoading flag for the duration where we processed the root of the source path but not for any of its sub-directories. Ok next idea.... Let's change the BeginInvoke inside the LoadImages method to be an Invoke call as well. Right that will work. We run that and it actually works quite well. I get feedback that the system is processing data via the ProgressBar and when the processing is complete, the ProgressBar disappers.

void Stackoverflow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
  if (e.PropertyName == "SourcePath")
  {
    IsLoading = true;
    Dispatcher.Invoke(new LoadImagesDelegate(LoadImages), DispatcherPriority.Background, SourcePath);
    IsLoading = false;
  }
}

delegate void LoadImagesDelegate(string folderLocation);
void LoadImages(string folderLocation)
{
  try
  {
    string[] files = System.IO.Directory.GetFiles(folderLocation);
    foreach (string file in files)
    {
      if (IsImage(file))
        Images.Add(file);
    }
  }
  catch (UnauthorizedAccessException) { } //Swallow

  try
  {
    string[] folders = System.IO.Directory.GetDirectories(folderLocation);
    foreach (string folder in folders)
    {
      Dispatcher.Invoke(new LoadImagesDelegate(LoadImages), DispatcherPriority.Background, folder);
    }
  }
  catch (UnauthorizedAccessException) { } //Swallow
}

But wait, this exactly follows the StackOverflow model we had in Part 2 of the series. On my system I don't actually have a deep enough directory structure that would cause an overflow (I don't think vista actually supports structures that deep). This concerns me that I am using a system that "could" just cause a StackOverflowException on me. Is my concern well founded? Maybe it is, maybe not. Conceding that a minor concern is not usually good grounds for declaring a proven practice let's consider the performance of our LoadImages method. What would happen if

  • the disk was under load?
  • the call was to a database or remote service (WCF, Web Service, etc...)
  • any other scenario where a method may take more than say 250ms

Well we can emulate the above scenarios by throwing in a Thread.Sleep in to our code. In my code I place a Thread.Sleep(300) into my LoadImage method at the start of the method. This represents latency of the disk/database/network. I think you may agree that 300ms is plausible value for latency on a slow network.

delegate void LoadImagesDelegate(string folderLocation);
void LoadImages(string folderLocation)
{
  Thread.Sleep(300);
  try
  {

If I run my updated code now the result is not good. The progress bar is jumpy, hovering over the button gives a delayed MouseOver effect and clicking on the items in the list is unresponsive.
What have we proved here?

  • The dispatcher can be used for very fast methods to interact with UI elements
  • Using Dispatcher.Invoke method is a option when we actually want the properties of a blocking call
  • The responsiveness of our application can be effected by external interfaces when we call them via the Dispatcher.

So where are we now? Hopefully you have a better understanding of the dispatcher. We like the dispatcher but we are aware of its features and its limitations. The following should be well understood now:

  1. We can achieve simple rendering effects with StoryBoards. These can be asynchronous and WPF does all the hard work to ensure a responsive UI.
  2. The UI is single threaded and performing work on the UI thread can kill UI responsiveness.
  3. The UI thread will have a Dispatcher to enable queuing of work for it to do.
  4. A Dispatcher can only belong to one thread.
  5. UI Controls are DispatcherObjects and can only be updated on the thread they were created on. Conveniently they also expose a property to the Dispatcher that owns them.
  6. You can queue tasks for the UI thread by using the Invoke and BeginInvoke methods on the thread's Dispatcher.
  7. When queuing tasks for the dispatcher you can set a priority for the task.
  8. Dispatcher.Invoke method queues a task and waits for its completion. Dispatcher.BeginInvoke queues a task and does not wait for its completion.
  9. Any task queued for the UI thread will be completed in its entirety before performing any other task. Therefore all tasks performed by the Dispatcher will cause some loss in responsiveness on the UI.

So basically we have arrived at a point where understand the WPF threading model enough to recognise that tasks with long or unknown delays cannot be perform on the UI thread, either via Control Constructors, event handlers, Dispatcher.Invoke or Dispatcher.BeginInvoke. This leads us to our next part in the series Multi-Threaded programming in WPF

Next we discover how to perform true multi-threaded programming in WPF to really get the UI responsive in Part 4.

Previous - The WPF Threading model and the dispatcher in Responsive WPF User Interfaces in Part 2.

Back to series Table Of Contents

Working version of the code can be found here