Home > c#, Windows Forms > Background Worker Thread Code Sample with event handlers and cross thread invoke

Background Worker Thread Code Sample with event handlers and cross thread invoke

The Problem

So you want to create a windows form application. It needs to do the following:

  1. Do some work where its processing multiple items
  2. Have a progress bar that updates the progress of the application as it runs.

Seems easy.. well here are the complexities:

  1. You want the main process to run on a background thread, so that the UI thread is available to update and render
  2. The background thread will need to raise events back to the main application to notify the application of the background thread progress.
  3. The progress bar will need to be updated to reflect the progress.
  4. The call to update the UI will crash because it is actually doing a cross-thread call, (the background thread, is trying to update the UI thread and this will crash.

This will walk you through the pattern for implementing this solution.

Background Worker Thread

This as the name suggests is a thread of execution that runs in the background in the application leaving the UI thread to do what it is good at, UI rendering.

If you were to try and have a method that processed 1000 records @ 2 seconds per record, and tried to update the progress bar on processing each record the UI would not render the progress bar as the UI thread is caught up in the process of doing the work.
Doing the work on the background thread leave the UI thread to update the UI whilst the background thread gets on with doing the work.

What is the background thread and how do I create one?

First of all lets create a visual studio solution. I am going to create an empty windows form application called WorkerUtility. I will place the following controls on it.

  1. A button btnGo – labeled Go
  2. A progress bar; and
  3. A label called lblProgress – to show the % complete

The background worker thread can be found in the toolbox under the “Components” section.
Drag the background worker thread onto you form and you will end up with a control called backGroundWorker1.

It should look like this:

Set the following properties on the background worker control

  • WorkerReportsProgress true

Double click on the “Go” Button and setup the following Code

private void btnGo_Click(object sender, EventArgs e)
{
    backgroundWorker1.RunWorkerAsync();
}

Performing the Work and Raising Events

I am going to simulate processing a 126 items @ Random number of seconds between 0 and 2 seconds, by looping and pausing the background thread. You could be doing anything in the processing block, for example, read data from the database loop through the rows and perform file system tasks based upon the data?

I will be performing work but each time an item is processed I want to tell the UI thread so that it can update the progress bar. To do this I will need to use events and I am going to create some custom event args.

Create a class called ProgressUpdatedEventArgs like the following:

using System;

namespace WorkerUtility
{
    public class ProgressUpdatedEventArgs : EventArgs
    {
        public ProgressUpdatedEventArgs(int total, int progress)
        {
            this.Total = total;
            this.Processed = progress;
        }

        public int Processed { get; private set; }

        public int Total { get; private set; }
    }
}

I will encapsulate the logic in a class called Processor.cs. Create a class as follows:
This will house the event handler and raise events back to the UI thread

The main point of Interest in the following code is the creation of the event handler on the page with a delegate method declaration and an event handler. The RaiseEvent method is called on each file that is processed.

using System;

namespace WorkerUtility
{
    public static class Processor
    {
        public delegate void ProgressUpdatedEvent(ProgressUpdatedEventArgs progressUpdated);

        public static event ProgressUpdatedEvent ProgressUpdated;

        /// <summary>
        /// Main execution method that does the work
        /// </summary>
        public static void Execute()
        {
            int total = 126 // this creates funny percentages
            Random randomGenerator = new Random();
            for (int i = 0; i < total; i++)
            {
                // Do some processing here

                double delay = (double)randomGenerator.Next(2) + randomGenerator.NextDouble();

                int sleep = (int)delay * 1000;

                System.Threading.Thread.Sleep(sleep);

                RaiseEvent(total, i + 1);
            }
        }

        private static void RaiseEvent(int total, int current)
        {
            if (ProgressUpdated != null)
            {
                ProgressUpdatedEventArgs args = new ProgressUpdatedEventArgs(total, current);
                ProgressUpdated(args);
            }
        }
    }
}

Cross-Thread method invoke, handling the callback

Now we need to set up an event handler in the main form so we can update the UI when the event is raised.

On the Background Worker Switch to the “Events” properties and double-click on “DoWork” to create the “DoWork” event handler.

Go to the _DoWork event handler and change as follows:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    Processor.ProgressUpdated += new Processor.ProgressUpdatedEvent(Processor_ProgressUpdated);
    Processor.Execute();
}

void Processor_ProgressUpdated(ProgressUpdatedEventArgs progressUpdated)
{

}

So this is where the tricky bit comes in. We need to implement the _ProgressUpdated callback.
If we were to go directly to update the UI, we would get a cross thread exception and the program would crash.

Cross-thread operation not valid

Cross-thread operation not valid: Control 'progressBar1' accessed from a 
thread other than the thread it was created on.

If you check the InvokeRequired method it will return true in this scenario so we need to perform a cross thread call to get from the background worker thread, that the “ProgressUpdated” event is raised on, to the UI thread to make the changes in the UI.

This is done as follows, firstly you need a delegate for the method invoke method callback and an Update Method to handle performing the UI Update.

public delegate void ProgressUpdatedCallaback(ProgressUpdatedEventArgs progress);

private void Processor_ProgressUpdated(ProgressUpdatedEventArgs progressUpdated)
{
	if (InvokeRequired)
	{
		Invoke(new ProgressUpdatedCallaback(this.UpdateProgress), new object[] { progressUpdated });
	}
	else
	{
		UpdateProgress(progressUpdated);
	}
}

private void UpdateProgress(ProgressUpdatedEventArgs args)
{
	if (progressBar1.Maximum != args.Total)
	{
		// initial setup
		progressBar1.Minimum = 0;
		progressBar1.Maximum = args.Total;
		progressBar1.Style = ProgressBarStyle.Continuous;
	}

	progressBar1.Value = args.Processed;

	if (args.Total > 0)
	{
		double progress = args.Processed / (args.Total * 1.0);
		lblProgress.Text = progress.ToString("P2");
	}

	Application.DoEvents();

}

Here is what it looks like running

Here is the full working code sample in a VS2010 solution.
WorkerUtility

VN:F [1.9.22_1171]
Rating: 10.0/10 (1 vote cast)
Background Worker Thread Code Sample with event handlers and cross thread invoke, 10.0 out of 10 based on 1 rating
Categories: c#, Windows Forms Tags:
  1. Mark G
    July 31st, 2012 at 04:11 | #1

    Thank you very much for your post. Your solution was exactly what I was looking for. I actually modified you solution slightly to allow for the updating of multiple progress bars and a rich text box, here is link to the solution: http://stackoverflow.com/a/11727026/100283

    VA:F [1.9.22_1171]
    Rating: +1 (from 1 vote)
    • jcrawfor74
      July 31st, 2012 at 20:11 | #2

      No problem. I often need to do little utility style applications and I always forget how to setup the event handlers, and the delegate events so this is a basic template for the core of how to implement a background thread example. Glad it helped.

      VN:F [1.9.22_1171]
      Rating: 0 (from 0 votes)
  1. No trackbacks yet.