3 Jul
2009

WPF Multithreading: Using the BackgroundWorker and Reporting the Progress to the UI.

Category:UncategorizedTag: , , , , :

I can’t count the number of times someone has asked me about running a time consuming task on a separate thread, but at the same time show a progress dialog with up-to-the-second percentage updates being displayed to the user. Multithreading can be confusing at first, but if you just take it one step at a time, it really isn’t all that bad. 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 Source Code.

Your WPF application may need to perform intensive tasks that consume large amounts of time. Executing these tasks on a separate thread allows you to maintain the responsiveness of the UI while the task is performed. Enter the BackgroundWorker. The BackgroundWorker is the recommended way to run time consuming tasks on a separate, dedicated thread, leaving the UI responsive.

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();

Cancelling the Background Process

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();

Reporting the Progress of a Background Process

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();

Using the Dispatcher to Access Controls on Another Thread

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 Dispacther object for a UI element from its Dispatcher property.

System.Windows.Threading.Dispatcher aDisp = Button1.Dispatcher;

Dispatcher provides two main methods that you will use; Invoke and BeginInvoke. Both methods allows you to call a method safely on the UI thread. The BeginInvoke method allows you to call a method asynchronously, and the Invoke method allows you to call a method synchronously.

Putting It All Together

Lets say I have a time consuming task in my application.  I want to execute this time consuming task on a background thread, but I also want to show a modal progress dialog that shows a message and the percent of the completed task. I also want to allow the user to cancel the process at anytime. So the first thing I need to do is create my progress dialog window that has a label to display my percent completed, a progress bar to graphically show the progress, and a cancel button so the user can cancel the process.

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

I need to expose two properties; ProgressText to set my label, and ProgressValue to set the progress bar value. I also need to expose an event so when the user hits the cancel button the process will stop.

   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.

<Button Height="23" Name="btnDispacther" Click="btnDispacther_Click">Using Dispatcher</Button>

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();
}

That’s it. See, I told you it wasn’t that bad. Now that you have a basic understanding of multithreading, try to see how creative you can get on implementing it in your WPF application.

30 thoughts on “WPF Multithreading: Using the BackgroundWorker and Reporting the Progress to the UI.

  1. One thing you might want to be aware of is the priority of your dispatcher work items, and how that plays into the priority of the synchronization context of the BackgroundWorker, and the priority *it* invokes on the UI. You might need to use reflector to work this out.

    Specifically: If you use updateprogress on the background worker, along with the Completed event, will be marshalled back onto the UI thread with I believe .Normal dispatcher priority. This means its it’s almost 100% guarenteed’d to be invoked almost immediately. This is often fine, but something to be aware of. if you want the notifications to be processed at a lower priority, you’ll have to go off road and invoke on the dispatcher directly.

  2. @Dominic Hopton

    Good point, and something I didn’t cover in this post. When using Invoke, or BeginInvoke, you can set the DispatcherPriority property, which determines the priority with which the delegate is executed. In addition, the Invoke method allows you to set a time period for the Dispatcher to wait before abandoning the invocation.

  3. Very cool article. I am wondering would using this approach also be good for loading a ListView whose DataContext is an ObservableCollection. I am thinking that if the loading of data is a time consuming process moving this to a backgrould worker would keep the UI responsive while the ObservableCollection is being loaded, and in turn the UI (ListView) could show the data already loaded while the remaining data is being loaded in the background?

  4. @Bruce Wallwin

    That’s good question. One problem with BackgroundWorker is the fact that you cannot pass anything through userState in ReportProgress, because it will be always null – it’s a bug in .NET IMO.

  5. Funny, I haven’t had that problem. Any state I pass from ReportProgress always comes through to the ProgressChanged event handler.

    This is an example of how I would use it with “Processing” being the UserState.

    worker.ReportProgress(percentage, “Processing”);

    Then get the UserState in the ProgressChanged event handler

    text = string.Format(“{0} {1}%”, args.UserState.ToString(), args.ProgressPercentage.ToString());

  6. Great article, this is exactly what I need for a bulk reporting tool i am creating. However I’ve having trouble converting this anonymous methods to vb.

    Any help on converting the below and relating function to vb would be much appreciated.

    worker.DoWork = delegate(object s, DoWorkEventArgs args){

    }

  7. Brian,
    I guessed that was the case but wasn’t 100% sure. So thanks for confirming that, I had a play with it last night and got it working.

    I declared a withevents private variable at page level and used the regular event handlers as you mentioned.

    Thanks for your reply.

  8. Hi Brian,

    I need your ideas to proceed with BackGroundWorker threads for my requirement.

    I have a C# Windows Application that processes a directory/sub-directories containing huge number of .xml files, say 100,000 files and perfoms time-consuming I/O operation such as parsing xml files(I am currently querying those files using Linq) while updating the user of every file being processed. In fact, I have 3 such I/O operations to be performed on the same set of files(so all the 3 tasks will basically share the same resources/directory/files for their own independent requirements).

    Also, I currently have a Windows Form associated with 3 different class files, one for each I/O task. I need to implement multithreading so that I can run all the 3 tasks at the same time, while updating the UI controls such as Label- one per task/thread to keep the user informed about the background process.

    What is the best way of proceeding about such kind of task in a single application?(if possible..). Kindly suggest me.

    Thanks in advance

  9. @Nigama

    Well one thing you will definitely have to watch out for is your files being locked by one thread while a different thread is trying to access it too. What I am trying to say is that if you have three I/O tasks to perform on a file, you can only do one I/O operation at a time. You cannot write to a file at the same time you try to read from a file. So I would recommend three seperate background workers. When the first I/O operation completes, in the worker.RunWorkerCompleted start your next operation and so forth.

  10. very good article
    ok i have one Question  what if i want to take some data from the UI and pass it to thread and get back the result (specifically i want to Render many canvases to bitmaps and display it as ImageSource )
    i used the BackgroundWorker to do this but it raise an exception said
    InvalidOperationException

  11. Hi Brian, your style to elaborate the topic is very easy. I am just in a confusion that I have to call a method i.e “MyProc()” in the background. Can you please let me know where I can call this method using Dispatcher example.

    Regards,

    Shehzad

  12. You do all the work in the background worker and use the dispatcher to pass information to the UI.

  13. Thanks a lot Brian, Can you please give me price of code for my learning, as I am very dull in learning new things…

Comments are closed.