WPF Multithreading: Using the BackgroundWorker and Reporting the Progress to the UI
In this blog , I am going to show you how to create a multithreaded application that shows a progress dialog which shows real time progress to the user.
First lets start with how to get started with multithreading in WPF. If you don’t want to read how this actually works and just want to get the source and start playing ,here is the
Running a Background Process
The RunWorkerAsync method starts the execution of the background process by raising the DoWork event. The code in the DoWork event handler is executed on a separate thread.
int maxRecords = 1000;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
for (int x = 1; x < maxRecords; x++)
{
System.Threading.Thread.Sleep(10);
}
};
worker.RunWorkerAsync();
Providing Parameters to the Process
Your background process may required one or more parameters, such as the address of a file to download. You can provide a parameter in the RunWorkerAsync method that will be available as the Argument property in the DoWork event handler.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
string path = (string)args.Argument;
//do something
};
worker.RunWorkerAsync("c:myFile.txt");
Returning a Value from the Process
You might want to return a value from a background process, such as a result from a calculation. You can return a value by setting the Result property of the DoWorkEventArgs in the DoWork event handler. Then, this value can be retrieved from the Result property of the RunWorkerCompletedEventArgs parameter int the RunWorkerCompleted event handler.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
args.Result = CalculationMethod();
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
object result = args.Result;
};
worker.RunWorkerAsync();
You may want to allow the user to cancel a long running process. In order to support this you must first set the WorkerSupportsCancellation to true. You call the CancelAsync mtehod to attempt to cancel the process. When you call the CancelAsync method it sets the CancellationPending property of the BackgroundWorker to true. Then you must check for the value of the CancellationPending property in the DoWork event handler, and if it is true, set the Cancel property of the DoWorkEventArgs parameter to true.
int maxRecords = 1000;
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
for (int x = 1; x < maxRecords; x++)
{
//check if there is a cancelat
if (worker.CancellationPending)
{
args.Cancel = true;
return;
}
System.Threading.Thread.Sleep(10);
}
};
worker.RunWorkerAsync();
You can report the progress of a background process back to the primary thread by calling the ReportProgress method. This method raises the ProgressChanged event and allows you to pass a parameter that indicated the percentage of progress that has been completed. Make sure you set the WorkerReportsProgess property of the BackgroundWorker to true.
int maxRecords = 1000;
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
for (int x = 1; x < maxRecords; x++)
{
System.Threading.Thread.Sleep(10);
worker.ReportProgress(Convert.ToInt32(((decimal)x / (decimal)maxRecords) * 100));
}
};
worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
{
int percentage = args.ProgressPercentage;
};
worker.RunWorkerAsync();
At times, you may want to change the user interface from a worker thread. For example, you may want to enable or disable buttons, or show a modal ProgressBar that provides more detailed progress information than is allowed by the ReportProgress method. The WPF threading model provides the Dispatcher class for cross thread calls. By using the Dispatcher, you can safely update your user interface from background worker threads.
You can get a reference to the Dispatcher object for a UI element from its Dispatcher property.
System.Windows.Threading.Dispatcher aDisp = Button1.Dispatcher;
Putting It All Together
<Window x:Class="MultiThreading.ProgressDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ProgressDialog" Height="115" Width="300" WindowStyle="ToolWindow" WindowStartupLocation="CenterOwner">
<Grid>
<StackPanel>
<Label x:Name="lblProgress" Content="0%"/>
<ProgressBar x:Name="progress" Height="25" IsIndeterminate="False"></ProgressBar>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="btnCancel" Click="btnCancel_Click">Cancel</Button>
</StackPanel>
</StackPanel>
</Grid>
</Window>
Now on my main application I need a button to execute this long running process and show my progress dialog.
1: public string ProgressText
2: {
3: set
4: {
5: this.lblProgress.Content = value;
6: }
7: }
8:
9: public int ProgressValue
10: {
11: set
12: {
13: this.progress.Value = value;
14: }
15: }
16:
17: public event EventHandler Cancel = delegate { };
18:
19: private void btnCancel_Click(object sender, RoutedEventArgs e)
20: {
21: Cancel(sender, e);
22: }
Now on my main application I need a button to execute this long running process and show my progress dialog.
Now lets code the background worker.
//our bg worker
BackgroundWorker worker;
//our progress dialog window
ProgressDialog pd;
private void btnDispacther_Click(object sender, RoutedEventArgs e)
{
int maxRecords = 1000;
pd = new ProgressDialog();
//hook into the cancel event
pd.Cancel += CancelProcess;
//get our dispatcher
System.Windows.Threading.Dispatcher pdDispatcher = pd.Dispatcher;
//create our background worker and support cancellation
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
for (int x = 1; x < maxRecords; x++)
{
if (worker.CancellationPending)
{
args.Cancel = true;
return;
}
System.Threading.Thread.Sleep(10);
//create a new delegate for updating our progress text
UpdateProgressDelegate update = new UpdateProgressDelegate(UpdateProgressText);
//invoke the dispatcher and pass the percentage and max record count
pdDispatcher.BeginInvoke(update, Convert.ToInt32(((decimal)x / (decimal)maxRecords) * 100), maxRecords);
}
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
pd.Close();
};
//run the process then show the progress dialog
worker.RunWorkerAsync();
pd.ShowDialog();
}
//our delegate used for updating the UI
public delegate void UpdateProgressDelegate(int percentage, int recordCount);
//this is the method that the deleagte will execute
public void UpdateProgressText(int percentage, int recordCount)
{
//set our progress dialog text and value
pd.ProgressText = string.Format("{0}% of {1} Records", percentage.ToString(), recordCount);
pd.ProgressValue = percentage;
}
void CancelProcess(object sender, EventArgs e)
{
//cancel the process
worker.CancelAsync();
}
Comments
Post a Comment