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

Asynchronus MVVM... Stop the Dreaded Dead GUI Problem in WPF

Rate me:
Please Sign up or sign in to vote.
3.50/5 (14 votes)
9 Nov 2010CPOL8 min read 92.1K   1.2K   28   37
Shows how to implement an asynchronous Model in the MVVM pattern.

Introduction

Many a developer has found the dreaded GUI freeze problem simply by calling a method which takes a long time to execute. The reason for this is that the "long outstanding job" will execute on the same Thread as the GUI program. This stops all events from being posted and handled, thus locking up the User Interface and rendering the application unusable until the background job is complete! Not a very professional way to deliver a product indeed.

This site has many articles related to this topic, many of them are very good. Some are complicated, other require knowledge of the internals regarding the solution. This solution offers something a bit different. It takes an older technology, BackGroundWorker(s), and adds in the ICommand interface, a Delegate here, and an Event there, and we have a fully asynchronous and easy solution to this common issue. Perhaps the best part of this is that you can take this project and easily change it to suit your needs without knowing too many details.

Using the code

Simply download the code and run it in Debug mode to see it work. This is the main screen:

AsychronousMainWindow.png

The top row of buttons are the TabItems in the Main TabControl. You'll default to the first tab which has two buttons: "Start the Asynchronous Job" and "Stop the Asynchronous Job". Once you start the job, you will see a progress bar at the bottom begin to show the status of the work. Press the "Stop" button and the background work will stop.

To see the impact of the BackGroundWorker on the main GUI thread, just press any of the other tabs while the progress bar is working. You can browser the web, or you can view the code diagrams of how this was designed.

Points of Interest

The BaseCommand Class

So what is the BaseCommand all about? It is a BackgroundWorker that implements the ICommand interface. Agile programming techniques teach us to abstract away the commonality. The BaseCommand class is an attempt to do that.

C#
public class BaseCommand :  BackgroundWorker, ICommand
{
    public bool canexecute = true;
    public event EventHandler CanExecuteChanged;

    //------------------------------------------------------------------
    public BaseCommand()
    {
        this.WorkerSupportsCancellation = true;
        this.WorkerReportsProgress = true;
        this.DoWork += new DoWorkEventHandler(BWDoWork);
        this.ProgressChanged += 
          new ProgressChangedEventHandler(BWProgressChanged);
        this.RunWorkerCompleted += 
          new RunWorkerCompletedEventHandler(BWRunWorkerCompleted);
    }

    //------------------------------------------------------------------
    public virtual void BWRunWorkerCompleted(object sender, 
                        RunWorkerCompletedEventArgs e)
    {
    }

    //------------------------------------------------------------------
    public virtual void BWProgressChanged(object sender, 
                        ProgressChangedEventArgs e)
    {
    }

    //------------------------------------------------------------------
    public virtual void BWDoWork(object sender, DoWorkEventArgs e)
    {
    }

    //------------------------------------------------------------------
    public virtual bool CanExecute(object parameter)
    {
        return true;
    }


    //------------------------------------------------------------------
    public virtual void Execute(object parameter)
    {
    }
}

As a BackgroundWorker, we find that some fundamental settings are set in the base class. This worker does support cancellation, it reports progress, and the events for doing the work. It pre-wires the ProgressChanged event handler as well as the RunWorkerCompleted event handler. Note however, that the methods themselves are virtual. This allows the concrete implementation to override these methods and implement what is desired.

The ICommand interface is the virtual CanExecute method that defaults to true and the Execute method (also virtual), both meant to be overridden in the concrete class. One other small note, there is a var named (lowercase) canexecute. We'll discuss this a bit later.

The Concrete BaseCommand Class Named AsychronousCommand

Inheriting the BaseCommand class as shown below, the concrete class first defines two delegates. One is for ProgressChanged and takes a parameter of type int that represents the progress (in percentage). The other delegate is the DataReady signature which, in this case, takes an ObservableCollection of type string. This is the preliminary set up for allowing any registered listeners to receive "feedback". Finally, there are two events built on those delegates which will be used for the "loosely coupled" communication in the View Model.

C#
public class AsynchronusCommand : BaseCommand
{
    <summary>
    /// This is the delegate definition to post progress back to caller via the 
    /// event named EHProgressChanged
    ///<summary>
    ///<param name="progress">
    // Hold the progress integer value (from 0-100)</param>
    //-----------------------------------------------------------------------
    public delegate void DlgProgressChanged(int progress);

    //-----------------------------------------------------------------------
    /// <summary>
    /// This is the delegate definition to post
    ///   a ObservableCollection back to the caller via the
    ///   event EHDataReady
    /// </summary>
    /// <param name="data">The signature
    ///   needed for the callback method</param>
    public delegate void DlgDataReady(ObservableCollection<string> data);

    //-----------------------------------------------------------------------
    //Static event allows for wiring up to event before class is instanciated
    /// <summary>
    /// This is the Event others can subscribe to,
    /// to get the post back of Progress Changed
    /// </summary>
    public static event DlgProgressChanged EHProgressChanged;

    //-----------------------------------------------------------------------
    //Static event to wire up to prior to class instanciation
    /// <summary>
    /// This is the event of which others can
    /// subscribe to receive the data when it's ready
    /// </summary>
    public static event DlgDataReady EHDataReady; 

    //-----------------------------------------------------------------------
    /// <summary>
    /// The Entry point for a WPF Command implementation
    /// </summary>
    /// <param name="parameter">Any parameter
    /// passed in by the Commanding Architecture</param>
    public override void Execute(object parameter)
    {
        if (parameter.ToString() == "CancelJob") 
        { 
            // This is a flag that the "other thread" sees and supports
            this.CancelAsync(); 
            return;
        }
        canexecute = false; 
        this.RunWorkerAsync(GetBackGroundWorkerHelper());
    }

So now we arrive to the ICommand part of this class. This is the part of the class that will receive notification from a bound Command in WPF via the Execute method (shown above). WPF calls the Execute method via the XAML command binding in this demo. The first check in the method is to see if the parameter passed in was to Cancel the Job; which, is just an arbitrary string of "CancelJob". If so, this Thread is Canceled via the code shown, and control is returned to the application. If this is not a Cancellation, then the code invokes the asynchronous RunWorkerAsync method (the built-in support from the BackgroundWorker class). But wait a second, what's this GetBackGroundWorkerHelper() call all about?

BackGroundWorkerHelper

I found out long ago that communicating to other threads was vastly simplified by creating a BackGroundWorkerHelper class. It is nothing more than a container for anything you want to pass in and out of another thread. In this sample below, we show two objects and a SleepIteration value being set. This is in the instantiation of the BackGroundWorkerHelper. The BackGroundWorkerHelper does have other variables in it we will see later.

C#
//-----------------------------------------------------------------------
/// <summary>
/// A helper class that allow one to encapsulate everything
/// needed to pass into and out of the 
/// Worker thread
/// </summary>
/// <returns>BackGroundWorkerHelper</returns>
public BGWH GetBackGroundWorkerHelper() 
{
    //The BGWH class can be anything one wants it to be..
    //all of the work performed in background thread can be stored here
    //additionally any cross thread communication can
    //be passed into that background thread too.
    BGWH bgwh = new BGWH(){obj1 = 1,
                           obj2 = 2,
                           SleepIteration = 200};
    return bgwh;
}

So what does the BackGroundWorkerHelper class look like? For this demo we arbitrarily set it up as shown below. The objects don't do anything other than to show you that anything may be passed to the other thread. Notice that the var Data is the ObservableCollection that will be populated in the background thread.

C#
//////////////////////////////////////////////////////////////////
/// <summary>
/// Background worker Class allows you to pass in as many objects as desired,
/// Just change this class to suit your needs. 
/// </summary>
public class BGWH 
{
    /// <summary>
    /// This demo chose a Object for the first "thing" to be passed in an out.
    /// </summary>
    public object obj1;  

    /// <summary>
    /// This is the second thing and shows another "object"
    /// </summary>
    public object obj2;

    /// <summary>
    /// An arbitrary integer value named SleepIteration
    /// </summary>
    public int SleepIteration;

    /// <summary>
    /// An observable collection
    /// </summary>
    public ObservableCollection<string> Data = 
           new ObservableCollection<string>(); 
}

Knowing that this BackGroundWorkerHelper is nothing more than a convenience class, it then becomes very simple to pass in complex data structures, DataTables, Lists, and the whole lot... Remember though that you don't want to pass in references to GUI objects because you will never be able to update them from these threads without some special code. Besides, this violates the rules of MVVM in that the ViewModel handles the content.

Doing the Work Asynchronously

So where is the "other thread"? The BackGroundWorker will spin off an asynchronous thread via the RunWorkerAsync method call discussed earlier. It will call the BWDoWork method which we hooked up via an EventHandler registration in the BaseCommand class. But notice here that we over-rode that method in the base class in the Concrete class, as shown below.

C#
//-----------------------------------------------------------------------
/// <summary>
/// This is the implementation of the Asynchronous logic
/// </summary>
/// <param name="sender">Caller of this method</param>
/// <param name="e">The DoWorkEvent Arguments</param>
public override void BWDoWork(object sender, 
                System.ComponentModel.DoWorkEventArgs e)
{
    //Ahh! Now we are running on a separate asynchronous thread
    BGWH bgwh = e.Argument as BGWH; 
    //always good to put in validation
    if (bgwh != null)
    {
        //we are able to simulate a long outstanding work item here
        Simulate.Work(ref bgwh, this);
    }
    //All the work is done make sure to store result
    e.Result = bgwh;
}

Notice also how easy it is to "unpack" the cargo. DoWorkEventArgs contains an argument which we know is a BackGroundWorkerHelper. How do we know? Because we passed it in via the GetBackGroundWorkerHelper() method call we discussed earlier. We unpack this helper class, and then check for null, and pass it in as a reference to the Simulate.Work method. All the Simulate.Work call does is to enter a loop, wait a bit, add data to the ObservableCollection in the BackGroundWorkerHelper class, and get this... It posts a Progress Event notification all the way back to the View via the ViewModel... Let's take a look.

Simulate Work Loop

There's a loop that uses the BackGroundWorkerHelper's SleepIteration count to control how many times to loop, then there's a call to report progress via the TheAsynchCommand.ReportProgress call, as shown below.

C#
 //-----------------------------------------------------------------------
public static void Work( ref Model.BGWH bgwh, BaseCommand TheAsynchCommand) 
{
    //shows how the BGWH can have many different control mechanisms
    //note that we pass it in as a reference which means all updates here are 
    //automatically reflected to any object that has this reference.
    int iteration = bgwh.SleepIteration;

    //This is iterative value to determine total progress. 
    double perIteration = .005;

    //simulate reading 200 records with a small delay in each..
    Random r = new Random();

    for (int i = 0; i < iteration + 1; i++)
    {
        System.Threading.Thread.Sleep(r.Next(250));

        //Update the data element in the BackGroundWorkerHelper
        bgwh.Data.Add("This would have been the " + 
                      "data from the SQL Query etc. " + i);

        //currentstate is used to report progress
        var currentState = new ObservableCollection<string>();
        currentState.Add("The Server is busy... Iteration " + i);
        double fraction = (perIteration) * i;
        double percent = fraction * 100;

        //here a call is made to report the progress to the other thread
        TheAsynchCommand.ReportProgress((int)percent, currentState);

        //did the user want to cancel this job? If so get out.
        if (TheAsynchCommand.CancellationPending == true)
        {
            // get out of dodge
            break;
        }
    }
}

Note that at the bottom of the loop, there is a check for Cancelling the work. If true, we simply break out of this loop and exit the class. Because the BackGroundWorkerHelper helper was passed as a reference, the data content is already set! It is ready to be posted back to the ViewModel.

Back to the AsynchronousCommand Class

Remember those event handlers we wired up and over-rode? When the Simulate.Work class wanted to report progress, it simply fired the event to report progress. The event handler in the AsynchronousCommand class picked it up (running on the GUI thread) and then fired an event to tell the View Model. This is shown below via the EHProgressChanged(progress) signal.

C#
//-----------------------------------------------------------------------
/// BackGround Work Progress Changed, runs on this object's main thread
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public override void BWProgressChanged(object sender, 
       System.ComponentModel.ProgressChangedEventArgs e)
{
    //allow for a Synchronous update to the WPF Gui Layer
    int progress = e.ProgressPercentage;

    //notify others that the progress has increased.
    EHProgressChanged(progress);
    EHDataReady((ObservableCollection<string>)e.UserState);
}

The EHDataReady is also signaled in an attempt to update the data before it was complete. Tests are inconclusive on whether or not data was able to be displayed on a ProgressNotify operation as shown. More work needs to be done there. However, as you will see in this application, the progress bar runs perfect.

One may ask, "How do we control the user from repeatedly kicking off the same command?" Remember that ubiquitous variable canexecute in the BaseCommand class? The method below which is called when the BackGroundThread is complete takes care of states.

C#
/// <summary>
/// Handles the completion of the background worker thread.
/// This method is running on the current thread and
/// can be used to update the execute method with information as needed
/// </summary>
/// <param name="sender">The sender of this event</param>
/// <param name="e">The Run WorkerCompleted Event Args</param>
public override void BWRunWorkerCompleted(object sender, 
       System.ComponentModel.RunWorkerCompletedEventArgs e)
{
  //ideally this method would fire an event
  //to the view model to update the data
  BGWH bgwh = e.Result as BGWH;
  var data = bgwh.Data;
  //notify others that the data has changed.
  EHDataReady(data);
  EHProgressChanged(0);
  canexecute = true;
}

The Asynchronous Commands' BWRunWorkerCompeted event handler, which was overridden from the BaseCommand class (and was pre-wired to receive the event), receives the notification. It parses the BackGroundWorkerHelper, and then fires two events, the first is the Data event (handled in the ViewModel), the other is to reset the ProgressBar to zero, and finally, canexecute is set to true, which will allow this command to be invoked again (canexecute stops multiple triggering of the same command). Note; however, this can also be accomplished in the Execute method by checking to see if the thread is busy; if it is, the user should be notified via a message. The solution, as is, will not allow the user to press the button more than once, which is a nice approach.

The ViewModel

Just how did the ViewModel handle these events? Looking at the first two lines of code in the ViewModel, we see that two EventHandlers were wired up to the static Model's AsynchronousCommand delegates. Remember way back at the top of the article were we defined these signatures?

C#
//-----------------------------------------------------------------------
public MainWindowViewModel()
{
    //the model will send a notification to us when the data is ready
    Model.AsynchronusCommand.EHDataReady += 
      new Model.AsynchronusCommand.DlgDataReady(
      AsynchronusCommandEhDataReady);
    //this is the event handler to update
    //the current state of the background thread
    Model.AsynchronusCommand.EHProgressChanged += 
      new Model.AsynchronusCommand.DlgProgressChanged(
      AsynchronusCommandEhProgressChanged);

And here are the methods in the ViewModel... Pretty simple really, as all they take is an integer for the progress in the first method, and an ObservableCollection of strings in the second.

C#
///-----------------------------------------------------------------------
// The event handler for when there's new progress to report
void AsynchronusCommandEhProgressChanged(int progress)
{
    Progress = progress;
}

//-----------------------------------------------------------------------
// The event handler for when data is ready to show to the end user
void AsynchronusCommandEhDataReady(ObservableCollection<string> data)
{
    Data = data;
}

From there, they merely invoke the property setter as shown. WPF takes care of the rest, and we see something like this happen when all is said and done.

The End of the Demo

Additional Comment

This article demonstrates an easy way to spin off another thread to do work, while the GUI thread remains unaffected. It shows how a BackGroundWorker and an ICommand interface can be wired up from XAML, using the MVVM pattern. The AsynchronusCommand (if you didn't already notice) is in the folder named Model, and not by accident. What we have here is a new way a Model can be implemented. It is "Loosely Coupled", and it runs in another thread. It even, without having a reference to the ViewModel, updates the View via the fired Events in the Simulate.Work class, which were handled by the ViewModel. Agile brought us to this point.

History

  • 11/01/2010 - Formatted code (JSOP).
  • 10/29/2010 - First edition.

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 States United States
ex: Mr Javaman

Comments and Discussions

 
QuestionAgile brought us to this point. Pin
Tibor Blazko8-Jan-18 7:36
Tibor Blazko8-Jan-18 7:36 
AnswerRe: Agile brought us to this point. Pin
Your Display Name Here27-Mar-20 7:54
Your Display Name Here27-Mar-20 7:54 
QuestionJust what I was looking for! Pin
badfish7225-Jun-13 5:38
badfish7225-Jun-13 5:38 
QuestionI'm quite happy with Pin
NikoStaub3-Aug-12 11:34
NikoStaub3-Aug-12 11:34 
AnswerRe: I'm quite happy with Pin
Your Display Name Here1-Oct-12 10:46
Your Display Name Here1-Oct-12 10:46 
GeneralMy vote of 5 Pin
adglopez5-Jan-11 12:14
professionaladglopez5-Jan-11 12:14 
My vote is 5
QuestionWhy not just use Task Parallel Library and follow a simple pattern? Pin
Jay R. Wren28-Nov-10 6:00
Jay R. Wren28-Nov-10 6:00 
AnswerRe: Why not just use Task Parallel Library and follow a simple pattern? Pin
Your Display Name Here1-Dec-10 7:43
Your Display Name Here1-Dec-10 7:43 
AnswerRe: Why not just use Task Parallel Library and follow a simple pattern? Pin
Puchko Vasili14-Apr-11 12:24
Puchko Vasili14-Apr-11 12:24 
AnswerRe: Why not just use Task Parallel Library and follow a simple pattern? Pin
Your Display Name Here27-Mar-12 5:05
Your Display Name Here27-Mar-12 5:05 
AnswerRe: Why not just use Task Parallel Library and follow a simple pattern? Pin
Your Display Name Here1-Oct-12 10:47
Your Display Name Here1-Oct-12 10:47 
GeneralComment.... Pin
Your Display Name Here22-Nov-10 3:03
Your Display Name Here22-Nov-10 3:03 
GeneralRe: Comment.... Pin
John Adams20-Feb-11 3:54
John Adams20-Feb-11 3:54 
GeneralMy vote of 3 Pin
KenThompson12-Nov-10 4:45
KenThompson12-Nov-10 4:45 
GeneralRe: My vote of 3 Pin
Your Display Name Here18-Nov-10 10:26
Your Display Name Here18-Nov-10 10:26 
GeneralMy vote of 4 Pin
Mamta D10-Nov-10 5:56
Mamta D10-Nov-10 5:56 
QuestionWhere are the downloads? Pin
bscaer9-Nov-10 7:38
bscaer9-Nov-10 7:38 
AnswerRe: Where are the downloads? Pin
Your Display Name Here9-Nov-10 13:46
Your Display Name Here9-Nov-10 13:46 
NewsPlease note that additional changes have been made per comments below. [modified] Pin
Your Display Name Here2-Nov-10 12:24
Your Display Name Here2-Nov-10 12:24 
GeneralRe: Please note that additional changes have been made per comments below. Pin
John Adams4-Nov-10 12:14
John Adams4-Nov-10 12:14 
GeneralArticle ammended per Sacha Barber and John Adams' concerns. Pin
Your Display Name Here1-Nov-10 4:14
Your Display Name Here1-Nov-10 4:14 
GeneralRe: Article ammended per Sacha Barber and John Adams' concerns. Pin
John Adams1-Nov-10 9:43
John Adams1-Nov-10 9:43 
GeneralRe: Article ammended per Sacha Barber and John Adams' concerns. Pin
Your Display Name Here2-Nov-10 2:38
Your Display Name Here2-Nov-10 2:38 
GeneralRe: Article ammended per Sacha Barber and John Adams' concerns. [modified] Pin
John Adams20-Feb-11 3:55
John Adams20-Feb-11 3:55 
GeneralMy vote of 3 Pin
Sacha Barber31-Oct-10 23:23
Sacha Barber31-Oct-10 23:23 

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.