Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

MultiThreading Using a Background Worker, C#

4.65/5 (51 votes)
12 Nov 2014CPOL7 min read 208.1K   7.6K  
This article will show how to use the most important features of a background worker to load the balance of your code between different working threads. It will also demonstrate how to take progress events from the background worker and how to cancel a currently running thread.

Introduction

When developing Windows Forms applications, you always notice that the user interface will freeze when your application is doing a time consuming operation, for example processing a large file or requesting data from a remote server. This is because your application is running on a single thread. This thread is responsible for rendering the user interface to the user, and also for handling all of your application events and methods. Thus, a lengthy operation will block your user interface till it is done. Today, what we will be doing is to move these lengthy operations to a different thread, thus keeping the user interface running smoothly while your operation is working on the other side.

Background

For this, we will be using the Microsoft BackgroundWorker class, more information on this class can be found here.

We will be creating a simple application that will be performing a time consuming operation, and displaying the final result to the user. This heavy operation will be carried on a different thread, and during its operation it will be constantly updating the user interface with its progress. We will also allow the user to cancel the operation at any time.

Please keep in mind that only the main thread will have access to the user interface, in other words, you cannot access any user control from the other thread. We will see more on this later.

Using the Code

I will be showing the code used in the application as we progress, and at the end, I will attach the final source code.

Creating the Solution

We will begin by creating a simple Windows Forms application form Microsoft Visual Studio, I will be using Visual Studio 2010. Create a new Windows Forms Application as in the below figure, I prefer to use C#, but you can use VB.NET if you like.

Image 1

Set up your form designer as below. I personally like to use table layout panels to organize my controls. This will also make it a lot easier for me to keep the controls in order if the form is expanded or resized. What we will need to add is a text box (set to multiline mode) to show the results coming from our working thread, a numeric box to allow us to choose a number, a start button and a cancel button.

Image 2

From the toolbox menu, under the Menus & Toolbars section, add a "Status Strip". This will allow us to add a status label, where we will be showing progress to the end user.

Image 3

Inside the status strip, click the small arrow on the left corner and add a "Status Label". Rename the label to lblStaus, and set its Text property to an empty string.

Image 4

Inside the code class, declare an object of type BackgroundWorker:

C#
private BackgroundWorker myWorker = new BackgroundWorker();

In the Form Constructor, initialize the following properties of the worker we just created:

  • The DoWork event handler, which will be called when the background worker is instructed to begin its asynchronous work. It is here inside this event where we do our lengthy operations, for example, call a remote server, query a database, process a file... This event is called on the new thread, which means that we cannot access the user controls from inside this method.
  • The RunWorkerCompleted event handler, which occurs when the background worker has finished execution has been canceled or has raised an exception.This event is called on the main thread, which means that we can access the user controls from inside this method.
  • The ProgressChanged event handler which occurs when the ReportProgress method of the background worker has been called. We use this method to write the progress to the user interface. This event is called on the main thread, which means that we can access the user controls from inside this method.
  • The WorkerReportsProgress property, which is needed to instruct the worker that it can report progress to the main thread.
  • The WorkerSupportsCancellation property, which is needed to instruct the worker that it can be canceled upon the user request.

Below is the complete code for the constructor after the declarations:

C#
public Form1()
{
    InitializeComponent();

    myWorker.DoWork+=new DoWorkEventHandler(myWorker_DoWork);
    myWorker.RunWorkerCompleted+=new RunWorkerCompletedEventHandler(myWorker_RunWorkerCompleted);
    myWorker.ProgressChanged+=new ProgressChangedEventHandler(myWorker_ProgressChanged);
    myWorker.WorkerReportsProgress = true;
    myWorker.WorkerSupportsCancellation = true;
}

Now let us declare the event handlers for our worker:

  • The DoWork event handler will take two parameters, a sender object, and a DoWorkEventArgs argument:
    C#
    protected void myWorker_DoWork(object sender, DoWorkEventArgs e)
    {
    
    }
  • The RunWorkerCompeleted event handler will take two parameters, a sender object, and a RunWorkerCompletedEventArgs argument:?
    C#
    protected void myWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
    
    }
  • The ProgressChanged event handler will take two parameters, a sender object, and a ProgressChangedEventArgs argument:?
    C#
    protected void myWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
    
    }

Now we will create a helper method that will simply accept an integer, multiply this integer by 1000, sleep for 250 milliseconds, and then return the result. This is just a simulation for a heavy operation that your application might do, you can change the sleep duration to any value you want. Please note that since this method will be called inside the DoWork event, it is the Background thread that will sleep for the given duration, and not the main thread. The function will be as follows:

C#
private int PerformHeavyOperation(int i)
{
    System.Threading.Thread.Sleep(250);
    return i * 1000;
}

Switch to the Designer mode, double click the Start button to begin handling its click event. What we will do is to capture the numeric value from the numeric up down control, pass this value to the asynchronous thread, and instruct the background worker to start working. We need to capture the numeric value at this stage, because once we are inside the new thread, we will not be able to capture any input from the user controls. To start the execution of the background thread, we will call the RunWorkerAsync method. This method can accept an object argument, this object will be passed to the background thread. Inside this object, we can put any values that we captured from the user controls. To pass more than one value, we can use an array of objects. Below is the complete code for the btnStart_Click event handler, Please note that a worker that is already in progress cannot be called again, you will get a runtime error if you attempt to do so.

C#
private void btnStart_Click(object sender, EventArgs e)
{
    int numericValue = (int)numericUpDownMax.Value;//Capture the user input
    object[] arrObjects = new object[] { numericValue };//Declare the array of objects
    if (!myWorker.IsBusy)//Check if the worker is already in progress
    {
        btnStart.Enabled = false;//Disable the Start button
        myWorker.RunWorkerAsync(arrObjects);//Call the background worker
    }
}

Now inside the DoWork event handler, we will do all of our heavy work. First, we will capture the objects we received from the main thread, then we will process them, finally we will pass the result back to the main thread, to be able to display it to the user. Keep in mind that only the main thread has access to the user controls. Also while we are processing the values, we will be continuously doing two things:

  • Reporting progress to the main thread using the ReportProgress method, to show the user where we are now.
  • Checking the CancellationPending property of the background worker, to check it the user has issued a cancellation command.

Finally, we will be putting our result inside the Result property of the DoWorkEventArgs argument to be captured by the main thread inside the RunWorkerCompleted event. Below is the complete code for our DoWork handler:

C#
protected void myWorker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker sendingWorker = 
    (BackgroundWorker)sender;//Capture the BackgroundWorker that fired the event
    object[] arrObjects = 
    (object[])e.Argument;//Collect the array of objects the we received from the main thread

    int maxValue = (int)arrObjects[0];//Get the numeric value 
            //from inside the objects array, don't forget to cast
    StringBuilder sb = new StringBuilder();//Declare a new string builder to store the result.

    for (int i = 1; i <= maxValue; i++)//Start a for loop
    {
        if (!sendingWorker.CancellationPending)//At each iteration of the loop, 
                    //check if there is a cancellation request pending 
        {
            sb.Append(string.Format("Counting number: {0}{1}", 
            PerformHeavyOperation(i), Environment.NewLine));//Append the result to the string builder
            sendingWorker.ReportProgress(i);//Report our progress to the main thread
        }
        else
        {
            e.Cancel = true;//If a cancellation request is pending, assign this flag a value of true
            break;// If a cancellation request is pending, break to exit the loop
        }
    }

    e.Result = sb.ToString();// Send our result to the main thread!
}

Now we will handle the ProgressChanged event. Here, we will just capture the integer value we have sent from the background thread when we called the ReportProgress method. Please note that you can pass objects of any datatype, by using the UserState of the ProgressChangedEventArgs argument. One thing you can do here other than showing a status message is to use a progress bar to show the current progress.

C#
protected void myWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    //Show the progress to the user based on the input we got from the background worker
    lblStatus.Text = string.Format("Counting number: {0}...", e.ProgressPercentage);
}

Now for the RunWokerCompleted event. Here, we first need to check if the worker has been canceled, or if an error had occurred. After that, we will collect the result that the background worker has calculated for us, and display it to the user:

C#
protected void myWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled && 
    e.Error == null)//Check if the worker has been canceled or if an error occurred
    {
        string result = (string)e.Result;//Get the result from the background thread
        txtResult.Text = result;//Display the result to the user
        lblStatus.Text = "Done";
    }
    else if (e.Cancelled)
    {
        lblStatus.Text = "User Canceled";
    }
    else
    {
        lblStatus.Text = "An error has occurred";
    }
    btnStart.Enabled = true;//Re enable the start button
}

One last thing remains, we need to implement the cancellation button. Double click the cancel button, and inside the code class, call the CancelAsync method of the background worker. This will set the CancellationPending flag to true. We were checking this flag at each loop iteration back in the DoWork event handler. From this, we can conclude that terminating a backgroundworker currently in progress will not happen immediately, if the background worker is doing something, we have to wait for the worker to finish it before being able to cancel the operation. Below is the code for the btnCancel_Click:

C#
private void btnCancel_Click(object sender, EventArgs e)
{
    myWorker.CancelAsync();//Issue a cancellation request to stop the background worker
}

 

Finally, here is a snapshot of the application in progress:

Image 5

And here is a snapshot when the operation completed:

Image 6

You can download the source code from the link at the top of this article, thanks for following up. :)

Next: Check my updated basic sample article on the new C# Task Factory: 

 

Multithreading using Task Factory, C#, Basic Sample

License

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