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:
The top row of buttons are the TabItem
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.
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.
public class AsynchronusCommand : BaseCommand
{
<summary>
public delegate void DlgProgressChanged(int progress);
public delegate void DlgDataReady(ObservableCollection<string> data);
public static event DlgProgressChanged EHProgressChanged;
public static event DlgDataReady EHDataReady;
public override void Execute(object parameter)
{
if (parameter.ToString() == "CancelJob")
{
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.
public BGWH GetBackGroundWorkerHelper()
{
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.
public class BGWH
{
public object obj1;
public object obj2;
public int SleepIteration;
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, DataTable
, List
, 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.
public override void BWDoWork(object sender,
System.ComponentModel.DoWorkEventArgs e)
{
BGWH bgwh = e.Argument as BGWH;
if (bgwh != null)
{
Simulate.Work(ref bgwh, this);
}
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.
public static void Work( ref Model.BGWH bgwh, BaseCommand TheAsynchCommand)
{
int iteration = bgwh.SleepIteration;
double perIteration = .005;
Random r = new Random();
for (int i = 0; i < iteration + 1; i++)
{
System.Threading.Thread.Sleep(r.Next(250));
bgwh.Data.Add("This would have been the " +
"data from the SQL Query etc. " + i);
var currentState = new ObservableCollection<string>();
currentState.Add("The Server is busy... Iteration " + i);
double fraction = (perIteration) * i;
double percent = fraction * 100;
TheAsynchCommand.ReportProgress((int)percent, currentState);
if (TheAsynchCommand.CancellationPending == true)
{
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.
public override void BWProgressChanged(object sender,
System.ComponentModel.ProgressChangedEventArgs e)
{
int progress = e.ProgressPercentage;
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.
public override void BWRunWorkerCompleted(object sender,
System.ComponentModel.RunWorkerCompletedEventArgs e)
{
BGWH bgwh = e.Result as BGWH;
var data = bgwh.Data;
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?
public MainWindowViewModel()
{
Model.AsynchronusCommand.EHDataReady +=
new Model.AsynchronusCommand.DlgDataReady(
AsynchronusCommandEhDataReady);
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 string
in the second.
void AsynchronusCommandEhProgressChanged(int progress)
{
Progress = progress;
}
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.
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.