BackgroundWorker Threading Example

Read this article in your language IT | EN | DE | ES


Hey there. I’ve been pretty wrapped up in SQL lately but I wanted to get some posts that are mini-tutorials to illustrate some concepts difficult to grasp for beginners. I’ve found plenty of resources on the internet but not all of them are easy, or cut and dry. Let’s see if I can make adding threading to a Windows Form application easier to understand…

First let’s create a Windows Form application and drag some objects onto it from the toolbox:

Form

I’ve got a start and cancel button to control the running of the backgroundworker1 progress. The “Seconds to Run" numeric up and down control will dictate how long the overall progress is and we’ll get some feedback through the status label and the progress bar.

Our form runs in its own thread and without creating a separate thread all the work gets done in the forms thread. That is problematic on long running processes because it causes the form to become unresponsive.

I ran into this problem when I created an application to pre-compile ASP.NET sites. I was using Visual Web Developer which didn’t come with a Publish feature so I had to build one myself. On a larger site with a lot of code it took several seconds to pre-compile. This left my form “locked up” and a couple times Windows popped up asking me if I wanted to close the unresponsive application.

Adding threading to your application is easy, just drag and drop the BackgroundWorker control from the Components section of the toolbox onto your workspace in the section below your form.

BackGroundWorker

A quick look at the properties shows I set both the WorkerReportsProgress and WorkerSupportsCancellation to True. You don’t have to do this but it seems silly to use a background worker and not report its progress or allow us to cancel it.

Properties

Double click on the backgroundWorker1 component to create a new DoWork method in the code just like you would for a button.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    //your work goes here...   
}

We also need to add event handlers to control the separate thread from our form. We’ll add event handlers for ProgressChanged and  RunWorkerCompleted by going to the events list for the background worker’s properties and double clicking the events to get the event handler code.

 Events

 

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    //update our form when our backgroundWorker makes progress
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //reset the form
}

Now we’ll double click our buttons on the form to get their click event handlers.

private void startButton_Click(object sender, EventArgs e)
{
    //set the form and start the work
}
private void cancelButton_Click(object sender, EventArgs e)
{
    //cancel the work and reset the form
}

Next we’ll add our work method passing in an object of type integer that will be the upper boundary in a for-loop. To start with, we’ll stub out the method though.

private int MyCustomFunction(int numberArg)
{
    int result = 0;
    //my custom work stuff goes here...
    return result;
}

I’ve added a return value of type integer although in my code I will not be acting on it. You could just as easily set the RichTextBox.Text to the value that is returned and cast it as a string in some meaningful message like “Completed 10 seconds of work.”. I didn’t here, but I thought I’d comment on what you could do…its up to you. You’re the developer! :)

Well, that’s the plumbing that we need to get some stuff done in the background. To get the basic functionality of it you only need to add the DoWork and RunWorkerCompleted methods. Add the ProgressChanged event handler if you set the WorkerReportsProgress property to True.

Next we’ll add the call to our custom method passing in our e.Argument

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    int result = MyCustomFunction(int.Parse(e.Argument.ToString()));
}

The type conversion here is a little “wonky-b-jonky” but the custom work function expects an integer and to get that we have to cast our e.Argument to an integer, but we don’t get to do that without first casting it as a string. It’s like fishing…for a legal object type.

On to our background worker methods…

One of the things about the background worker is you get a progress percentage “out of the box”. In our ProgressChanged event we update our form where the statusLabel is a numeric representation of the percent of work complete and the progress bar is set from the e.ProgressPercentage. I’m also adding a “.” to the RichTextBox control because that is used sometimes to illustrate something running in the background without exposing the details of the work being done.

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    statusLabel.Text = e.UserState.ToString();
    progressBar1.Value = e.ProgressPercentage;
    traceOutputRichTextBox.Text += ".";
}

The WorkerCompleted method is pretty basic too, we just set the value of the controls on the form to a “base” state.

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    progressBar1.Value = 0;
    startButton.Enabled = true;
    cancelButton.Enabled = false;
    traceOutputRichTextBox.Text = "";
}

Next, we’ll also add some form beautification to our button event handlers. The “special sauce” here is in the cancel button’s click event where we call the CancelAsync() method of our background worker thread.

private void startButton_Click(object sender, EventArgs e)
{
    startButton.Enabled = false;
    cancelButton.Enabled = true;
    backgroundWorker1.RunWorkerAsync(int.Parse(numericUpDown1.Value.ToString()));
}
        
private void cancelButton_Click(object sender, EventArgs e)
{
    progressBar1.Value = 0;
    startButton.Enabled = true;
    cancelButton.Enabled = false;
    traceOutputRichTextBox.Text = "";

    backgroundWorker1.CancelAsync();
}

 

**** A note about the DoWork event and arguments….

Since e.Argument is an object, you can create a separate class with properties that has all of the information you want to pass as an argument. You’re not locked into using a single type, I just used an integer for simplicities sake. There is a brief thread in the MSDN forums that has a good example of not creating a container class but passing an object array which will do nicely for just a few arguments.

http://social.msdn.microsoft.com/Forums/en-US/Vsexpressvb/thread/3f09a2cd-2d0a-4d67-9f24-eb2c40480c7a/

Now for the meat and potatoes…our custom work function. We’ve got our loop function with a check to see if our background worker has been told to cancel (CancelAsync() in the cancel button click). If it has not been told to cancel then we calculate the progress and get the worker to announce what it is doing in the ReportProgress method. This will fire off the ProgressChanged method which sets the form controls values updating the UI about what the background worker is doing.

I put in a Thread.Sleep to simulate a delay so the UI gets updated in a realistic way. Normally the work you do would be causing the delay. The “break” allows us to exit the method as soon as we detect the CancellationPending. I’ve never really liked “breaks” because they feel  a little impulsive to me. That aside, I wanted it in there to show that you can abort an operation immediately if needed. After all, this article is more for beginners…

private int MyCustomFunction(int numberArg)
{
    int result = 0;

    int i;
    int loopToNumber = numberArg;

    for (i = 0; i <= loopToNumber; i++)
    {
        if (backgroundWorker1.CancellationPending)
        {
            progress = ((i * 100) / loopToNumber);
            message = progress.ToString(); 
            backgroundWorker1.ReportProgress(progress, message);
            break;
        }
        else 
        {
            progress = ((i * 100) / loopToNumber);
            message = progress.ToString(); 
            backgroundWorker1.ReportProgress(progress, message);
        }
        //pretend this is harder than it is...
        Thread.Sleep(1000);
    }

    return result;
}

Well, that’s it! Build the application and try it out. Pretty simple right? Its easy to add a background worker to your application to perform a long running processes.

Working

It still “feels” like more code than I care to deal with sometimes, and depending upon the situation you can create threads in other ways.

In my next post I’ll show you how to do it with a Timer object for a dead-easy, lightning-fast, all-be-it “dumb” solution to creating a separate thread. If you don’t care about knowing what the thread is doing and just can’t stomach this much code…stick around for my next post!

--Robert

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Comments

Add comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading