Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

WPF: A Most Useful Threading Component

Rate me:
Please Sign up or sign in to vote.
4.97/5 (54 votes)
30 Dec 2009CPOL19 min read 120.7K   1.2K   147   44
A threading component that shows failures/busy status and data.

Contents

Introduction

As many of you may know, I write quite a lot about UI work and WPF technology, and I have in the past written articles about Threading, and have also written a great lot about my own WPF MVVM Framework Cinch, which includes a lot of stuff to get you up and running doing WPF the MVVM way.

Well, one thing that has always bugged me while working with WPF is threading and doing something in the background and keeping my UI nice and free to do other stuff. Sure I can spawn a new Thread or use the ThreadPool and even use the funky BackgroundTaskManager class inside of Cinch which does help a lot in terms of fetching data on an internally held BackgroundWorker and is completely unit testable, as discussed in this Cinch article. I just felt that more improvements could be made.

For example, what I would want to be able to do, is have a ViewModel (to allow Binding and testing) that was being used as a DataContext for some View, to be able to accept some parametised work delegate that would be performed on a background thread, and would show some busy animation or status while the threading operation was happening, and would then either show an error status in the View somewhere if the background threading operation failed, or would show me the actual data that the background thread fetched that I requested in the parametised work delegate, if there was no failure while fetching the relevant data.

I should point out that the code contained here does spawn a new BackgroundWorker per threading operation, where each operation is expected to return some single definable bit of data, such as a single List<SomeClass>, or even some other very expensive time consuming data to fetch. My intent was never to have some global threading manager that fetches all and sundry in one hit, it's more micro managing the data.

In laymen's terms, think of it like this (where "I" and "Me" in this italic paragraph means the demo app code provided in this article ):

You want me to show a List<Contact> that could take some time to fetch, fair enough. I'll create a BackgroundWorker to do that and manage that background activity, and let you know how it's going, and when I am done, I'll return you a List<Contact>, or some error message. Oh, and you also want me to show a List<BankAccount> on the same View that will also take some time to fetch. Well now, for that, I will need to return another type of List. In fact, it will be a List<BankAccount> so I am going to be needing another BackgroundWorker to do that, and return you your List<BankAccount>.

I know this could potentially spawn a few threads, but internally, the BackgroundWorker makes use of the ThreadPool, which is ace, so I do not feel it's an issue, as the management of Threads is done for us by the .NET Framework. Obviously, if you have hundreds of lists of data on one View, this article may not be for you at all, and you should stop reading right here. If on the other hand you have a few lists of data or a few expensive bits of data to fetch, this code could well be for you.

Anyway, that is how I see it working, and after messing around for a while, I think I have done just that.

There are a few assumptions that I have made, which are as follows:

  • That people are using WPF, and are pretty competent with it. This is not a beginner article at all.
  • That people are using the MVVM pattern.
  • That people think it's a good idea to have parts of your View show error messages if the data that was supposed to be fetched failed to happen.
  • That people are happy with the idea of an item of data (such as a List of data) or expensive bits of data being fetched by a dedicated BackgroundWorker manager object, which we will get to later.

The Actual Problem

The problem is really like this: from what I have seen, very few people take the time to show the user much feedback at the best of times, let alone when a long running threading operation is happening. In fact, some UIs just boldly do everything on the UI thread, and let the user wait. OK, some people do change the icon to an hour-glass or something, and disable buttons etc., while something is happening.

Wouldn't it be better if we had some component that kept the user in the loop? They start a long running operation either by requesting it or by opening up some View that requires it, and when that happens, the user is constantly shown what is going on as a progress bar is shown while we are doing the work, and if it fails, we show them why, right there in the UI, where it's visible, not in some MessageBox that disappears as soon as they accept "OK", and then try and ring support only to be asked what the MessageBox message said. Oh, I closed that, sorry. And of course, when it all goes to plan, simply hide the progress bar, do not show any failure UI, and just show them what they wanted that has now been fetched.

That is the problem, the way I see it.

The Solution

So what is the solution? Well, the solution is obviously to come up with something that fixes the problem, right?

Well, this article does fix all of the things mentioned above in the problem description. The way it does it, is by using various bits of WPF technology, and threading bits and pieces.

As I previously mentioned, the code supplied in the attached demo app is assuming that you are using the MVVM pattern. I am not saying you could not get it to work without using MVVM; it's just, I think MVVM is fab, and it works, and that is the only way I will be describing in this article's text.

What Does the Solution Look Like

The demo code makes use of a simple idea; we use a WPF UserControl to wrap a bit of content, which would be the UI data that you wish to fetch in a background thread.

So when it runs, it looks something like this:

Image 1

The attached code makes use of the AdornerLayer to show different Adorners depending on the state of the background threading operation. If the background threading operation is busy, the BusyAdorner is shown. If the background threading operation failed and is not busy, the FailedAdorner is shown. If the background threading operation is not busy and not failed, hide both the BusyAdorner and FailedAdorner, which just leaves the original data shown in the UI, which has now been fetched on a background thread.

You can read more about how the Adorners work in the Adorners section below.

How Does the Solution Work

In this series of sub sections, I will outline how the attached demo code is structured. It should be noted that you will not need to know about a lot of this, you would simply include it in your project and do what is recommended in the How to Go About Using This Idea in Your Own WPF App section. But if you are like me, you will want to know about the details in full before knowing what you need to do to use it, so that is what this section is all about.

Foreword About the Demo App

The demo app attached obviously has to demonstrate the total idea. As such, there is a retarded bit of code that allows the user to pick or choose whether the background threading operation should fail during runtime. The user is able to do this by clicking a button on the demo code's UI.

Image 2

Now this is obviously only test code, and should never be used in production code; all it does is toggle a "ShouldFail" flag that is checked within the background thread delegate. Which is like this in the demo code:

C#
Func<Dictionary<String, Object>, 
   ThreadableItem<List<StuffData>>> taskFunc = (inputParams) =>
{
    try
    {
        // REGION BELOW FOR TESTING ONLY
        // REGION BELOW FOR TESTING ONLY
        // REGION BELOW FOR TESTING ONLY
        // REGION BELOW FOR TESTING ONLY
        // REGION BELOW FOR TESTING ONLY
        #region TEST EXAMPLE CODE, YOU SHOULD NOT DO THIS IN PRODUCTION CODE
        if (ShouldFail)
        {
            throw new InvalidOperationException(
                "InvalidOperationException occurred\r\n\r\n" +
                "This Exception has been raised inside " + 
                "the Window1ViewModel delegate " +
                "which is the actual payload for the " + 
                "ThreadableItemViewModel<T> TaskFunc delegate.\r\n\r\n" +
                "Which is obvioulsy not what you would " + 
                "do in a production system.\r\n\r\n" +
                "You would more typically catch your own business Exceptions " +
                "(say from talking to WCF) and then rethrow them. " +
                "This just demomstrated how all this hangs together");
        }
        else
        {
            List<StuffData> data = new List<StuffData>();
            for (int i = 0; i < (Int32)inputParams["loopMax"]; i++)
            {
                data.Add(new StuffData(String.Format("The Text Is {0}",i),i));
                Thread.Sleep(5); //simulate time going by
            }
            //work is done at this point, so return new ThreadableItem with the
            //actual data in it, and no failure message
            return new ThreadableItem<List<StuffData>>(data, String.Empty);
        }
        #endregion
    }
    //something went wrong so return a new ThreadableItem with the failed
    //message in it
    catch(Exception ex)
    {
        return new ThreadableItem<List<StuffData>>(null, ex.Message);
    }
};

It is just there to demonstrate how the code is intended to work. In real production code, you would not do this; you would more likely do something like this:

C#
Func<Dictionary<String, Object>, ThreadableItem<List<Client>>> taskFunc = (inputParams) =>
{
    try
    {
        try
        {
            //Obtain a list of Clients that work in particular BusinessArea 
            Service<IGateway>.Use((client) =>
            {
                RetrieveDataByQueryRequest request = new RetrieveDataByQueryRequest();
                request.Query = new Query().SelectAll(BusinessEntityType.Client)
                    .Where(new Filter("BusinessAreaId", 
                        FieldOpeartor.Equals,(Int32)inputParams["businessArea"]);

                RetrieveDataByQueryResponse response = 
                    (RetrieveDataByQueryResponse)client.Execute(request);

                return new ThreadableItem<List<Client>>(response.Clients, String.Empty);
            });
        }
        //catch WCF FaultExceptions
        catch (FaultException<SerializationException> sex)
        {
            //throw actual Exception which is caught it outer catch
            throw new BusinessException("A serialization issue has occurred");
        }
        //catch WCF FaultExceptions
        catch (FaultException<InvalidArgumentException> iaex)
        {
            //throw actual Exception which is caught it outer catch
            throw new BusinessException("One of the arguments is invalid");
        }

    }
    //something went wrong so return a new ThreadableItem with the failed
    //message in it
    catch(Exception ex)
    {
        return new ThreadableItem<List<Client>>(null, ex.Message);
    }
};

While this may look a bit hairy right now, do not worry, we will be going through that rather nasty looking Func<T,TResult> delegate declaration a bit later. The important thing to note is that the demo code uses some gash test code that you should see as just that demo code that needs replacing with your real code, something like the code chunk seen above.

MVVM

As I stated, the demo code uses the MVVM pattern. You might ask yourself why. The reasons are simple; MVVM allows us to bind directly to our ViewModel, and the ViewModel also serves as a very nice unit testing entry point. As such, there are various ViewModels in the attached demo code, the main ones being:

ViewModelBase: Simple INotifyPropertyChanged base class for all other ViewModels to inherit from.

Here is what this looks like, nothing too fancy:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace ThreadingComponent
{
    /// <summary>
    /// Base class for all ViewModels, simply provides INPC support
    /// </summary>
    public class ViewModelBase : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Notify using pre-made PropertyChangedEventArgs
        /// </summary>
        /// <param name="args"></param>
        protected void NotifyPropertyChanged(PropertyChangedEventArgs args)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, args);
            }
        }

        /// <summary>
        /// Notify using String property name
        /// </summary>
        protected void NotifyPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}

ThreadableItemViewModelBase: This is a base class for ThreadableItemViewModel<T> based ViewModels, and allows the UserControl that handles the AdornerLayer and wraps the data to hook up INotifyPropertyChanged property changed watchers without having to care about the actual generic type of the current background threading operation. This class basically just exposes some common properties that all ThreadableItemViewModel<T> based ViewModels will need to use.

This is what this looks like:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ThreadingComponent
{
    /// <summary>
    /// Provides a base class for <see cref="ThreadableItemViewModelBase">
    /// ThreadableItemViewModel</see> classes. The reason a base class is needed
    /// is that the <see cref="ThreadableHostControl">ThreadableHostControl</see> 
    /// needs to listen to certain INPC value changes to swap the Adorners in and out. 
    /// Now as inheritors of this class use Generics the <see cref="ThreadableHostControl">
    /// ThreadableHostControl</see> needs a common INPC ancestor that it can listen
    /// to INPC property changes from. This base class enables that behaviour.
    /// </summary>
    public class ThreadableItemViewModelBase : ViewModelBase
    {
        #region Data
        private Boolean isBusy = false;
        private Boolean failed = false;
        private String  errorMessage = String.Empty;
        #endregion

        #region Public Properties
        public bool IsBusy
        {
            get { return isBusy; }
            set 
            {
                isBusy = value;
                NotifyPropertyChanged("IsBusy");
            }
        }

        public bool Failed
        {
            get { return failed; }
            set
            {
                failed = value;
                NotifyPropertyChanged("Failed");
            }
        }

        public String ErrorMessage
        {
            get { return errorMessage; }
            set
            {
                errorMessage = value;
                NotifyPropertyChanged("ErrorMessage");
            }
        }
        #endregion
    }
}

ThreadableItemViewModel<T>: This generic ViewModel inherits from ThreadableItemViewModelBase. Its job is to manage the background threading operation. As such, it has various properties that facilitate the management of the running of the background threading activity. The T generic should be the Type that you want for the background threading activity. So for example, if I expected to get a List<Client> back, T would be List<Client>.

You would need to have one of these ThreadableItemViewModel<T> for each background activity you wish to perform. In the demo app, that is only one List of some imaginary Model data called "StuffData", so in my main ViewModel (Window1ViewModel), I have a single instance of ThreadableItemViewModel<List<StuffData>> which is used to manage the retrieval of a List<StuffData>.

Here is what this looks like:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Threading;

namespace ThreadingComponent
{
    /// <summary>
    /// This ViewModel contains all the data required to run a work delegate
    /// (the TaskFunc Func<Dictionary<String,Object>,ThreadableItem<T>>) and also
    /// exposes methods to allow the work delegate to be run on a new thread. The 
    /// Data obtained by running the background thread is exposed on the Data
    /// property, and the BgWorker is also exposed as a property so that it can be
    /// used within Unit tests.
    /// </summary>
    /// <typeparam name="T">The return type of the threading operation</typeparam>
    public class ThreadableItemViewModel<T> : ThreadableItemViewModelBase
    {
        #region Data
        private ThreadableItem<T> data;
        private Func<Dictionary<String,Object>,ThreadableItem<T>> taskFunc;
        private BackgroundTaskManager<ThreadableItem<T>> bgWorker;
        private Dictionary<String, Object> parameters = null;
        #endregion

        #region Private Methods
        /// <summary>
        /// Sets up the actual BackgroundTaskManager<T> component
        /// </summary>
        private void SetupWorker()
        {
            if (taskFunc == null)
                throw new NullReferenceException("TaskFunc can not be null");

            bgWorker = new BackgroundTaskManager<ThreadableItem<T>>(
                 () =>
                 {
                     return taskFunc(Parameters);
                 },
                 (result) =>
                 {
                     Data = result;
                 });

            BgWorker.BackgroundTaskStarted -= BackgroundTaskStarted;
            BgWorker.BackgroundTaskCompleted -= BackgroundTaskCompleted;

            BgWorker.BackgroundTaskStarted += BackgroundTaskStarted;
            BgWorker.BackgroundTaskCompleted += BackgroundTaskCompleted;
        }

        /// <summary>
        /// Event that is raised when the background work is Completed. This event
        /// is raised by the internal BackgroundTaskManager<T> component
        /// </summary>
        private void BackgroundTaskCompleted(object sender, EventArgs args)
        {
            //The order that these properties IS IMPORTANT, as it dictates
            //which Adorner will be shown
            IsBusy = false;
            Failed = !String.IsNullOrEmpty(Data.Error);

        }

        /// <summary>
        /// Event that is raised when the background work is Started. This event
        /// is raised by the internal BackgroundTaskManager<T> component
        /// </summary>
        private void BackgroundTaskStarted(object sender, EventArgs args)
        {
            //The order that these properties IS IMPORTANT, as it dictates
            //which Adorner will be shown
            IsBusy = true;
            Failed = false;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Run the work delegate on a new thread
        /// </summary>
        public void Run()
        {
            SetupWorker();
            bgWorker.RunBackgroundTask();
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// The actual work delegate. This must ALWAYS take a 
        /// Dictionary<String,Object> as the 1st parameter, which is the collection
        /// of parameters that the work delegate may need to do its work. This can obviously
        /// be null. The T is the expected return type for the threading operation
        /// </summary>
        public Func<Dictionary<String,Object>,ThreadableItem<T>> TaskFunc
        {
            set
            {
                taskFunc = value;
            }
        }

        /// <summary>
        /// A Dictionary of key/value pairs, where each pair is a parameter
        /// that the work delegate (TaskFunc) may need to do its work
        /// </summary>
        public Dictionary<String, Object> Parameters
        {
            get { return parameters; }
            set { parameters = value; }
        }

        /// <summary>
        /// The BackgroundTaskManager exposed so you can use it within
        /// Unit tests. See the actual <see cref="BackgroundTaskManager">
        /// BackgroundTaskManager</see> code for how to do that
        /// </summary>
        public BackgroundTaskManager<ThreadableItem<T>> BgWorker
        {
            get { return bgWorker; }
        }

        /// <summary>
        /// The actual Data that is the result of running the 
        /// background threading operation
        /// </summary>
        public ThreadableItem<T> Data
        {
            get { return data; }
            set
            {
                data = value;
                if (data != null)
                    this.ErrorMessage = data.Error;
                NotifyPropertyChanged("Data");
            }
        }
        #endregion
    }
}

The most important things to note in this bad boy are the public properties:

  • TaskFunc: Which is a Func delegate that is the actual background work you want done.
  • Parameters: A Dictionary<String,Object> which is fed into the TaskFunc, Func delegate which is your parameters collection, which may be useful when running the background operation.
  • BgWorker: The actual BackgroundTaskManager<T> which is exposed to allow a Unit Test to maybe set up a AutoResetEvent and only Wait a specific amount of time for the operation to happen, before assuming it failed. The BackgroundTaskManager<T> class is straight out of my Cinch MVVM framework, and is discussed in this Cinch article.
  • Data: Is the actual data which is of Type ThreadableItem<T>, so what does one of those look like then? Well, ThreadableItem<T> is a simple class that represents the result of the background operation, so it has a DataObject and an Error. Only one of which is expected to be an actual value at any one time.

If the operation ran successfully, then the ThreadableItem<T>'s DataObject will be an instance of T that the user asked for, and the ThreadableItem<T>'s Error will be an empty string.

If the operation failed, the ThreadableItem<T>'s DataObject will be null, and the ThreadableItem<T> Error will be an Exception message string.

For clarity, here is what the ThreadableItem<T> class looks like:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ThreadingComponent
{
    /// <summary>
    /// This class represents a threadable item, which can have
    /// both an valid payload dataobject which is specified using
    /// the generic, and also an error string value
    /// </summary>
    /// <typeparam name="T">The type of the return value for the threading
    /// operation that will use a ThreadableItem</typeparam>
    public class ThreadableItem<T> : ViewModelBase
    {
        #region Data
        private T dataObject;
        private String error;
        #endregion

        #region Constructor
        public ThreadableItem(T dataObject, String error)
        {
            this.DataObject = dataObject;
            this.Error = error;
        }
        #endregion

        #region Public Properties
        public T DataObject
        {
            get { return dataObject; }
            set
            {
                dataObject = value;
                NotifyPropertyChanged("DataObject");
            }
        }

        public String Error
        {
            get { return error; }
            set 
            {
                error = value;
                NotifyPropertyChanged("Error");
            }
        }
        #endregion
    }
}

So far, all we have covered in the ViewModels, you will most likely not change, so there is one more to cover which is how to actually use the ones we have seen above. In the demo app, there is a single Window called Window1.xaml, this has a single ViewModel (to allow binding) called Window1ViewModel, which manages all the operations that Window1 wants to do.

Window1ViewModel is an exemple of how to use the rest of the codebase, though as I previously stated, does have test code in it so you can see how all this fits together, and you will need to change it in your production code. Again, I showed you an example of that earlier.

Commanding

Whilst not strictly part of the article's main thrust, I would recommend using some sort of DelegateCommand, or Josh Smith RelayCommand, or Marlon Grech SimpleCommand (which is what I use), all of which allow your UI to bind to a ViewModel exposed ICommand and actually run code in the ViewModel. The reason I would recommend ICommands is that you can disable a button in the ICommand.CanExecute directly in the ViewModel if there is a background threading operation in progress. For an example of this, see Window1ViewModel.

Obviously, this only applies if the threading operation happens as a result of a button click or something the user initiated.

Threading

Since this article is all about a background threading component/idea, you would expect there to be loads to say about Threads. Well, actually a lot of that is abstracted from you.

The steps involved are pretty much like this (you can use the attached demo Window1ViewModel code as a basis for writing your own ViewModel code to use this article's ideas/concepts).

Step 1: Expose property and pick T

Create a ViewModel, and expose a ThreadableItemViewModel<T> as a public property which you can then bind to using a ThreadableHostControl UserControl which is described in the next section, where the generic T must obviously be qualified with the correct type; for example, here is a valid property declaration:

C#
public ThreadableItemViewModel<List<StuffData>> ThreadVM
{
    get { return threadVM; }
}
Step 2: Wire up background work delegate

Now that you have exposed ThreadableItemViewModel<T> as a public property, and picked a return type for it, we can look at how to get the results in a background thread. This is easily achieved; all we have to do is set up the background work delegate, which is done as follows:

C#
Func<Dictionary<String, Object>, ThreadableItem<List<StuffData>>> taskFunc = (inputParams) =>
{
    try
    {
        // REGION BELOW FOR TESTING ONLY
        // REGION BELOW FOR TESTING ONLY
        // REGION BELOW FOR TESTING ONLY
        // REGION BELOW FOR TESTING ONLY
        // REGION BELOW FOR TESTING ONLY
        #region TEST EXAMPLE CODE, YOU SHOULD NOT DO THIS IN PRODUCTION CODE
        if (ShouldFail)
        {
            throw new InvalidOperationException(
                "InvalidOperationException occurred\r\n\r\n" +
                "This Exception has been raised inside the Window1ViewModel delegate " +
                "which is the actual payload for the " + 
                "ThreadableItemViewModel<T> TaskFunc delegate.\r\n\r\n" +
                "Which is obvioulsy not what you would do in a production system.\r\n\r\n" +
                "You would more typically catch your own business Exceptions " +
                "(say from talking to WCF) and then rethrow them. " +
                "This just demomstrated how all this hangs together");
        }
        else
        {
            List<StuffData> data = new List<StuffData>();
            for (int i = 0; i < (Int32)inputParams["loopMax"]; i++)
            {
                data.Add(new StuffData(String.Format("The Text Is {0}",i),i));
                Thread.Sleep(5); //simulate time going by
            }
            //work is done at this point, so return new ThreadableItem with the
            //actual data in it, and no failure message
            return new ThreadableItem<List<StuffData>>(data, String.Empty);
        }
        #endregion
    }
    //something went wrong so return a new ThreadableItem with the failed
    //message in it
    catch(Exception ex)
    {
        return new ThreadableItem<List<StuffData>>(null, ex.Message);
    }
};

//setup Threading task function
threadVM.TaskFunc = taskFunc;

The important thing to observe here are that we return a ThreadableItem<T> where T is of type List<StuffData>. And remember, a Func<T,TResult> is nothing more than a delegate that looks like this:

C#
public delegate TResult Func<T, TResult>(T arg);

So really, all we are just saying is that we have a delegate that takes Dictionary<String, Object> as an input parameter, and it returns ThreadableItem<List<StuffData>> as a return value. Simple, right?

The Dictionary<String, Object> acts as a collection of parameters that may be needed by the thread work item, so we need to configure it like so:

C#
//setup Threading parameters
Dictionary<String, Object> parameters = new Dictionary<String, Object>();
parameters.Add("loopMax", 30000);
threadVM.Parameters = parameters;

If your work delegate does not require any parameters to do its job, simply set Parameters = null.

Step 3: Run the Background Work Item

The last thing to do is to run the background work item, which is as easy as this:

C#
threadVM.Run();

Which will do the following inside the ThreadableItem<T> code:

C#
/// <summary>
/// Run the work delegate on a new thread
/// </summary>
public void Run()
{
    SetupWorker();
    bgWorker.RunBackgroundTask();
}

/// <summary>
/// Sets up the actual BackgroundTaskManager<T> component
/// </summary>
private void SetupWorker()
{
    if (taskFunc == null)
        throw new NullReferenceException("TaskFunc can not be null");

    bgWorker = new BackgroundTaskManager<ThreadableItem<T>>(
         () =>
         {
             return taskFunc(Parameters);
         },
         (result) =>
         {
             Data = result;
         });

    BgWorker.BackgroundTaskStarted -= BackgroundTaskStarted;
    BgWorker.BackgroundTaskCompleted -= BackgroundTaskCompleted;

    BgWorker.BackgroundTaskStarted += BackgroundTaskStarted;
    BgWorker.BackgroundTaskCompleted += BackgroundTaskCompleted;
}

This code makes use of a funky BackgroundTaskManager class inside of Cinch which does help a lot in terms of fetching data on an internally held BackgroundWorker and is completely unit testable, as discussed in this Cinch article. The important thing to note here is to see how this simply calls the original taskFunc property, which is the one that you just specified using the Func<T,TResult> delegate, remember?

C#
Func<Dictionary<String, Object>, ThreadableItem<List<StuffData>>> taskFunc = (inputParams) =>
{
  ....
  ....
  ....
  ....
};

//setup Threading task function
threadVM.TaskFunc = taskFunc;

It also passes the Func<T,TResult> delegate the parameters Dictionary<String, Object>, which are the parameters that can be used inside the threading delegate work item.

I guess I better prove this all works with a screenshot. Here is a screenshot using the worker item code above:

Image 3

Word of warning

The code above is just there to demonstrate how the failure code is intended to work. A reader alerted me to the fact that the previous article (yes, this is being posted again) took up 400MB of RAM. Now, the reason that was is my old worker delegate used to create 5,000,000 objects in memory, which as you can imagine was a real issue. Now, we would never actually do that in a production environment. I have modified the code to use a much more modest 30000 objects, which uses like 19MB of RAM. So thanks to that user for spotting that. I am glad it was not something stupid I had done. Phew.

In fact, after I published it again, the same user Insomniac Geek came up with another rather sensible suggestion, which is to use a few items (1000) and use a System.Threading.Thread.Sleep(5) to simulate some work, so that is what I do now. As even with 30,000 items, the background thread happened in milliseconds. So thanks user Insomniac Geek.

In real production code, you would not do this, you would more likely do something like this:

C#
Func<Dictionary<String, Object>, ThreadableItem<List<Client>>> taskFunc = (inputParams) =>
{
    try
    {
        try
        {
            //Obtain a list of Clients that work in particular BusinessArea 
            Service<IGateway>.Use((client) =>
            {
                RetrieveDataByQueryRequest request = new RetrieveDataByQueryRequest();
                request.Query = new Query().SelectAll(BusinessEntityType.Client)
                    .Where(new Filter("BusinessAreaId", 
                        FieldOpeartor.Equals,(Int32)inputParams["businessArea"]);

                RetrieveDataByQueryResponse response = 
                    (RetrieveDataByQueryResponse)client.Execute(request);

                return new ThreadableItem<List<Client>>(response.Clients, String.Empty);
            });
        }
        //catch WCF FaultExceptions
        catch (FaultException<SerializationException> sex)
        {
            //throw actual Exception which is caught it outer catch
            throw new BusinessException("A serialization issue has occurred");
        }
        //catch WCF FaultExceptions
        catch (FaultException<InvalidArgumentException> iaex)
        {
            //throw actual Exception which is caught it outer catch
            throw new BusinessException("One of the arguments is invalid");
        }

    }
    //something went wrong so return a new ThreadableItem with the failed
    //message in it
    catch(Exception ex)
    {
        return new ThreadableItem<List<Client>>(null, ex.Message);
    }
};

//setup Threading task function
threadVM.TaskFunc = taskFunc;

Adorners

Now that you know there is a ThreadableItemViewModel<T> property exposed that makes use of an internal ThreadableItem<T>, we can imagine making use of that in the UI. This is done via exposing a ThreadableItemViewModel<T> property in your own ViewModel, like this:

C#
public ThreadableItemViewModel<List<StuffData>> ThreadVM
{
    get { return threadVM; }
}

In this case, the generic T is obviously a List<StuffData>. So how do we then use this in the actual View? Well, let's have a look, shall we?

XML
<local:ThreadableHostControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
    ThreadableItem="{Binding ThreadVM}">

    <ListView Background="WhiteSmoke" BorderBrush="Black" BorderThickness="5"
              ItemsSource="{Binding ThreadVM.Data.DataObject}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Text" Width="500" 
                                DisplayMemberBinding="{Binding Path=Text}"/>
                <GridViewColumn Header="Age" Width="100" 
                                DisplayMemberBinding="{Binding Path=Age}"/>
            </GridView>
        </ListView.View>
    </ListView>
    
</local:ThreadableHostControl>

It can be seen that there is a ThreadableHostControl UserControl which makes use of this ThreadableItemViewModel<List<StuffData>> ThreadVM property exposed by the ViewModel.

There are two clever things going on here:

Let Content Be Content

In WPF, there are loads of different ways to do things, but I always like to keep my XAML as clean as I can. Part of the trick is understanding the framework, but also knowing what classes support what properties. A UserControl has a Content property, so let's just use that to host the data that we want to show which is fetched in the background thread. In this case, this will be a List<StuffData> bound to a ListView.

We could have done some tricks with several UI controls having their Visibility properties toggled, but that means more XAML, ouch. A better way is to let the content be content, and then we can host extra content on top of the content in a layer called the AdornerLayer.

If you do not know about the AdornerLayer, you should read this MSDN link: http://msdn.microsoft.com/en-us/library/ms743737.aspx.

Supporting Adorners

The ThreadableHostControl.ThreadableItem property is bound to an instance of ThreadableItemViewModel<T> which inherits from ThreadableItemViewModelBase, which as I mentioned earlier was the base class for all ThreadableItemViewModel<T> that supported a couple of properties namely:

  • IsBusy
  • Failed
  • ErrorMessage

So how does the ThreadableHostControl UserControl make use of the ThreadableItemViewModelBase property that it gets via a Binding? Well, that is quite interesting; let's have a look, shall we?

The ThreadableHostControls ThreadableItem DependencyProperty changed event calls an internal method called SetupPropertyWatcher():

C#
private static void OnThreadableItemChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue != null)
    {
        ((ThreadableHostControl)d).SetupPropertyWatcher(
            (ThreadableItemViewModelBase)e.NewValue);
    }
}

So looking into the SetupPropertyWatcher() method, we can see that it sets up the INotifyPropertyChanged property watchers for various properties of the ThreadableItemViewModelBase DP instance:

C#
/// <summary>
/// Watches the IsBusy/Failed INPC properties of the 
/// ThreadableItemViewModelBase and when they change, 
/// swaps in/out the correct Adorner to suit the current 
/// state of the ThreadableItemViewModelBase
/// </summary>
/// <param name="item">The ThreadableItemViewModelBase to 
/// watch INPC changes on</param>
private void SetupPropertyWatcher(ThreadableItemViewModelBase item)
{
    if (threadableItemObserver != null)
    {
        threadableItemObserver.UnregisterHandler(n => n.IsBusy);
        threadableItemObserver.UnregisterHandler(n => n.Failed);
    }
    threadableItemObserver = new PropertyObserver<ThreadableItemViewModelBase>(item);
    threadableItemObserver.RegisterHandler(n => n.IsBusy, this.IsBusyChanged);
    threadableItemObserver.RegisterHandler(n => n.Failed, this.FailedChanged);
}

Note: I am making use of Josh Smith's most excellent PropertyObserver to rig up watcher methods on the the original INPC object.

If we follow one of these through, say the Failed -> FailedChanged() method, we will see what happens:

C#
/// <summary>
/// Shows the FailedAdorner
/// </summary>
private void FailedChanged(ThreadableItemViewModelBase vm)
{
    if (vm.IsBusy)
        return;

    //If the users chose to throw an Exception on a null AdornerLayer
    //throw an Exception. The user may change this setting in the App.Config
    //which obviously impacts how the code works, but the Background
    //thread will still run, its just that the Adorners that this class
    //manages will not be shown. Which is not how the code was intended
    //to work. It would be better to find out why the AdornerLayer is null
    adornerLayer = AdornerLayer.GetAdornerLayer(this);

    if (shouldThrowExceptionOnNullAdornerLayer && adornerLayer == null)
        throw new NotSupportedException(
            "The ThreadableHostControl will only work correctly\r\n" +
            "if there is an AdornerLayer found and it is not null");

    if (adornerLayer != null)
    {
        if (vm.Failed)
        {
            SafeRemoveAll(new List<CustomAdornerBase>() { failedAdorner, busyAdorner });
            failedAdorner = new FailedAdorner(this, vm.ErrorMessage);
            adornerLayer.Add(failedAdorner);
        }
        else
        {
            SafeRemoveAll(new List<CustomAdornerBase>() { failedAdorner, busyAdorner });
        }
    }
    //repaint
    InvalidateControl();
}

See how this method is used to show a FailedAdorner in the AdornerLayer (if it's found and not null). The last piece of the puzzle is to see what the actual FailedAdorner looks like. Well, it's dead simple (there is a CustomAdornerBase base class, but I'll spare you that); it looks like this:

C#
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Collections.ObjectModel;

namespace ThreadingComponent
{
    /// <summary>
    /// Failed adorner that is shown when the threading 
    /// operation fails
    /// </summary>
    public class FailedAdorner : CustomAdornerBase, IResizableAdornerControl
    {
        #region Data
        private FailedUserControl failedUserControl;
        #endregion

        #region Constructor

        public FailedAdorner(FrameworkElement adornedCtrl, String text)
            : base(adornedCtrl)
        {
            failedUserControl = new FailedUserControl();
            failedUserControl.ErrorMessage = text;
            failedUserControl.Margin = new Thickness(0);
            host.Children.Add(failedUserControl);
        }

        #endregion 

        #region IResizableAdornerControl
        public void ResizeToFillAvailableSpace(Size availableSize)
        {
            host.Width = availableSize.Width - 5;
            host.Height = availableSize.Height - 5;
            failedUserControl.ResizeToFillAvailableSpace(availableSize);
        }
        #endregion
    }
}

All it does is host a FailedUserControl in the AdornerLayer. So the last step is to see what the FailedUserControl looks like. Well, here is its XAML:

XML
<UserControl x:Class="ThreadingComponent.FailedUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="Auto" Width="Auto" HorizontalAlignment="Stretch" 
             VerticalAlignment="Stretch">
    <Grid Margin="0,3,0,0" HorizontalAlignment="Stretch" 
          VerticalAlignment="Stretch" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" 
               HorizontalAlignment="Stretch" 
               Background="Black">
            <Image Source="../Images/Failed.png" 
               Height="45" Margin="5"/>
            <Label FontFamily="Arial" FontSize="24" 
               FontWeight="Bold" Content="Failed" 
               HorizontalAlignment="Left" 
               HorizontalContentAlignment="Left" 
               Foreground="White"
               VerticalAlignment="Center" 
               VerticalContentAlignment="Center"/>
        </StackPanel>


        <TextBox Grid.Row="1" 
             TextWrapping="Wrap" BorderThickness="0"
             BorderBrush="Transparent" Background="Transparent"
             IsReadOnly="True" FontSize="16" 
             FontWeight="Bold" 
             HorizontalAlignment="Stretch" 
             VerticalAlignment="Stretch"
             Text="The following error occurred whilst trying to obtain the data:"/>

        <TextBox Grid.Row="2" TextWrapping="Wrap" 
             BorderThickness="0" Margin="0,10,0,0"
             BorderBrush="Transparent" 
             Background="Transparent"
             IsReadOnly="True"
             HorizontalAlignment="Stretch" 
             VerticalAlignment="Stretch"
             Text="{Binding Path=ErrorMessage}"/>
    </Grid>

</UserControl>

It can be seen that the FailedUserControl uses the ErrorMessage property to display the error that occurred with the threading operation. This ErrorMessage property DP on the FailedUserControl was set on the FailedUserControl by the FailedAdorner in response to the actual ThreadableItemViewModelBase.Failed property changing.

Here is the code-behind for the FailedUserControl:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ThreadingComponent
{
    /// <summary>
    /// Control that is shown when the threading 
    /// operation fails
    /// </summary>
    public partial class FailedUserControl : UserControl, IResizableAdornerControl
    {
        #region Constructor
        public FailedUserControl()
        {
            this.DataContext = this;
            InitializeComponent();
        }
        #endregion

        #region DPs
        #region ErrorMessage

        /// <summary>
        /// ErrorMessage Dependency Property
        /// </summary>
        public static readonly DependencyProperty ErrorMessageProperty =
            DependencyProperty.Register("ErrorMessage", 
            typeof(String), typeof(FailedUserControl),
            new FrameworkPropertyMetadata(null));

        /// <summary>
        /// Gets or sets the ErrorMessage property.
        /// </summary>
        public String ErrorMessage
        {
            get { return (String)GetValue(ErrorMessageProperty); }
            set { SetValue(ErrorMessageProperty, value); }
        }

        #endregion
        #endregion

        #region IResizableAdornerControl
        public void ResizeToFillAvailableSpace(System.Windows.Size availableSize)
        {
            this.Height = availableSize.Height;
            this.Width = availableSize.Width;
        }
        #endregion
    }
}

Note: ThreadableItemViewModelBase.IsBusy works in the same way as this, except it has a BusyAdorner and a BusyUserControl.

Configuration

As I have stated in various places in the article, the attached demo code's ThreadableHostControl UserControl uses the AdornerLayer, which can occasionally come back as null (say, if you are using your own AdornerDecorator, or are in the middle of some crazy control such as an Infragistics one or a fat Ribbon), and as such, the attached demo code's ThreadableHostControl UserControl would not really work as planned.

To deal with this, the user can choose how this can be handled by using the App.Config setting "shouldThrowExceptionOnNullAdornerLayer" which directs the app to throw an Exception if the AdornerLayer can not be obtained.

If the user chooses to set the "shouldThrowExceptionOnNullAdornerLayer" App.Config value to "false", the threading should all work as expected; it is just the busy or failed Adorners that will not be shown, and the user will have to work out another way of dealing with the IsBusy and Failed states of the background threading operation.

Obviously, it would be better for everyone if this setting remains set to "true" and the user finds out why the AdornerLayer.GetAdornerLayer(this) is returning null.

How to Go About Using This Idea in Your Own WPF App

All you really have to do to use all this in your own app is:

  1. Copy the images to your Images/ folder; if you store images somewhere else, you will need to modify the FailedUserControl and BusyUserControl.
  2. Copy the following files to your own app:
    • BusyAdorner.cs
    • CustomAdornerBase.cs
    • FailedAdorner.cs
    • IResizableAdorner.cs
    • SimpleCommand.cs (if you want to use ICommands)
    • BusyUserControl.xaml/cs
    • CircularProgressBar.xaml/cs
    • FailedUserControl.xaml/cs
    • ThreadableHostControl.xaml/cs
    • Images folder: Put these where you like, but see Step 1
    • BackgroundTaskManager.cs
    • ThreadableItem.cs
    • PropertyObserver.cs
    • ThreadableItemViewModel.cs
    • ThreadableItemViewModelBase.cs
    • ViewModelBase.cs
    • You can look at Window1ViewModel as a base, but you should create your own ViewModel to suit your needs
    • App.config must contain the AppSettings key "ShouldThrowExceptionOnNullAdornerLayer"
  3. Create the right type of ThreadableItemViewModel<T> and expose that as a property from your ViewModel that drives your view, where T is nailed down to some Type
  4. Create the correct ThreadableItemViewModel<T>.TaskFunc property value which is expected to be of type Func<Dictionary<String,Object>,ThreadableItem<T>>

Limitations

There is no support for cancelling a threading work item delegate.

That's It

That is all I wanted to say right now. I have to say, for me, this was a real problem on a large scale WPF app, and this code will solve very real issues we have on that project. If you too feel that this could be useful in your WPF app, could you spare the time to make a comment or a vote? Many thanks.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralGreat Work as Usual... Need help [modified] Pin
Tradon-Dev20-Sep-10 4:24
Tradon-Dev20-Sep-10 4:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.