• (2 Pages)
  • +
  • 1
  • 2

Using the BackgroundWorker in C# Make your UI more responsive through multithreading

#1 Renagado  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 117
  • View blog
  • Posts: 388
  • Joined: 14-June 09

Posted 30 June 2009 - 03:33 PM

*
POPULAR

BackgroundWorker Basics in C#

This will be my first tutorial so I hope I can make myself clear. Also English isn't my first language so I apologize in advance for the inevitable language errors. ;)

Attached Image

What will this tutorial cover?

Making the BackgroundWorker do useful work for you, how to get the results back(I'm using List<int> as returndata, passing parameters to the BackgroundWorker, keeping track of progress and cancel the background process while it's running. Finally I'll show a way of collecting the results from the BackgroundWorker “on the fly” instead of waiting for all the calculations to be done.


Why use the BackgroundWorker?

You can use it to shift some heavy calculations, for example database access or file searching, to another thread and make the user interface more responsive. If both UI and the heavy calculations are ran within the same thread the UI appears to hang, making the average pc user think the program has crashed. So let's do it the decent way and use BackgroundWorker!

How to use the BackgroundWorker?

The BackgroundWorker may sound quite intimidating, but actually it's very easy and intuitive to do use once you've done the first time. So let's get going and make a simple application showing you the usage of the BackgroundWorker. In case you want to look up the BackgroundWorker class on MSDN, it's in the System.ComponentModel namespace.

The first thing you need to do is to add the BackgroundWorker to the application and the easiest way to do so is to drag it from the toolbox onto your form. It's under the components tab. You'll see the BackgroundWorker showing up as BackgroundWorker1 in the gray box under your form.
Attached Image

The BackgroundWorker is event-driven, so what you need to do to get the basics working is this:
-Invoke the BackgroundWorker's DoWork class from your application.
-Give DoWork some work to do by adding the code in the BackgroundWorker1.DoWork method.
-After the code in the DoWork method is done, the event RunWorkerCompleted is invoked.
-Through the RunWorkerCompleted method, we'll retrieve our return values from the 2nd thread

Ok, lets get coding! Double click on the DoWork event and add:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
		{
			List<int> temp = new List<int>();//our simulated return data
			for (int i=0; i<= 10; i++)
			{
				Thread.Sleep(300);//used to simulate lengthy operations, 
				temp.Add(i);//in this case 10*300ms=3s(add using System.Threading)
			}
			e.Result = temp;//return temp 
		}

And now for the RunWorkerCompleted event:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			data.AddRange((List<int>)e.Result);//copies return value to public list we declared before
			MessageBox.Show("Done");
		}


Finally make a button, change the text to Start, and add the following to the Button.Click event:


private void button1_Click(object sender, EventArgs e)
		{
			backgroundWorker1.RunWorkerAsync();//this invokes the DoWork event
		}

How do I pass an argument to the BackgroundWorker?

That's very easy. Let's try and pass the time the thread sleeps as an argument. First change the way we invoke the DoWork event to:

private void button1_Click(object sender, EventArgs e)
		{
			backgroundWorker1.RunWorkerAsync(300);//300 gives a total of 3 seconds pause
		}

And use the argument in our BackgroundWorker by changing the code to:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
		{
			int time = (int)e.Argument;
			List<int> temp = new List<int>();//our return data
			for (int i=0; i<= 10; i++)
			{
				Thread.Sleep(time);//used to simulate lengthy operations, 
				temp.Add(i);//in this case 10*300ms=3s(add using System.Threading)
			}
			e.Result = temp;//return temp 
		}

How do I cancel a BackgroundWorker operation halfway?

First set the WorkerSupportsCancellation property to true in the property window. Now add a button with text “Cancel”, double click on it and add:

private void button2_Click(object sender, EventArgs e)
		{
			backgroundWorker1.CancelAsync();//makes the backgroundworker stop
		}

Now we need to make the BackgroundWorker check if it has gotten the order to stop, and then break out of the loop. To do so, change the code to:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
		{
			int time = (int)e.Argument;
			List<int> temp = new List<int>();//our return data
			for (int i=0; i<= 10; i++)
			{
				if (backgroundWorker1.CancellationPending)//checks for cancel request
				{
					e.Cancel = true;
					break;
				}
				Thread.Sleep(time);//used to simulate lengthy operations, 
				temp.Add(i);//in this case 10*300ms=3s(add using System.Threading)
			}
			e.Result = temp;//return temp 
		}


Now if we press cancel, we get into trouble, so we also need to change RunWorkerCompleted to check for cancellations:


private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			if (e.Cancelled)//it doesn't matter if the BG worker ends normally, or gets cancelled,
			{			   //both cases RunWorkerCompleted is invoked, so we need to check what has happened
				MessageBox.Show("You've cancelled the backgroundworker!");

			}
			else
			{
				data.AddRange((List<int>)e.Result);//copies return value to public list we declared before
				MessageBox.Show("Done");
			}
		}

How do I keep track of progress?

First set the WorkerReportsProgress property to true. Then change the DoWork method to:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
		{
			int time = (int)e.Argument;
			List<int> temp = new List<int>();//our return data
			for (int i=0; i<= 10; i++)
			{
				if (backgroundWorker1.CancellationPending)//checks for cancel request
				{
					e.Cancel = true;
					break;
				}
				backgroundWorker1.ReportProgress(i * 10);//reports a percentage between 0 and 100
				Thread.Sleep(time);//used to simulate lengthy operations, 
				temp.Add(i);//in this case 10*300ms=3s(add using System.Threading)
			}
			e.Result = temp;//return temp 
		}

Now create a new ProgressBar, and then double click on the BackgroundWorker's ProgressChanged event and add:

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			progressBar1.Value = e.ProgressPercentage;
		}

Is there any other use for WorkerReportsProgress?

Yes there is. Let's say that instead of calculating all data, and then passing it on like we did, you want to add data “on the go”. For this we can use the Userstate parameter, which we can use to return any object, and then in the ProgressChanged event handler, we can cast this into the appropriate type and do with it whatever you need. As an example we'll use the values of i we looped through and add them to a second list. To keep it short, change:

backgroundWorker1.ReportProgress(i*10);

into:

backgroundWorker1.ReportProgress(i * 10,i);//reports ProgressPercentage AND Userstate

Now let's collect the data in a second List<int>:

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			progressBar1.Value = e.ProgressPercentage;
			data_new.Add((int)e.UserState);//casts the userstate into integer and adds it to a List
			
		}


This finishes this tutorial, I hope it was informable. Feedback would be appreciated!

Is This A Good Question/Topic? 8
  • +

Replies To: Using the BackgroundWorker in C#

#2 shezzy  Icon User is offline

  • D.I.C Head

Reputation: 28
  • View blog
  • Posts: 194
  • Joined: 28-January 07

Posted 04 July 2009 - 08:10 PM

I found this very helpful and informative. Thanks for your time, effort and contributions. Oh and your english is fine . kudos to you good sir! :^:
Was This Post Helpful? 0
  • +
  • -

#3 born2c0de  Icon User is offline

  • printf("I'm a %XR",195936478);
  • member icon

Reputation: 180
  • View blog
  • Posts: 4,667
  • Joined: 26-November 04

Posted 11 July 2009 - 09:29 AM

Good Job.
Was This Post Helpful? 0
  • +
  • -

#4 summey  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 38
  • Joined: 19-January 09

Posted 01 October 2009 - 01:05 PM

What is data.AddRange ? im getting The name 'data' dos not exist in teh current context ?

thank you
Was This Post Helpful? 0
  • +
  • -

#5 Grapevine  Icon User is offline

  • D.I.C Head

Reputation: 2
  • View blog
  • Posts: 55
  • Joined: 26-October 09

Posted 26 October 2009 - 09:57 AM

This is great! Thank you for the tut!!
Was This Post Helpful? 0
  • +
  • -

#6 Adkins  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 66
  • View blog
  • Posts: 560
  • Joined: 27-October 09

Posted 30 October 2009 - 06:35 AM

This is exactly what I needed. Thank you! :^:
Was This Post Helpful? 0
  • +
  • -

#7 Basoom  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 11-November 09

Posted 11 November 2009 - 02:33 AM

Thanks a lot for this helpful tutorial, But i have a question when using the progressbar . Is the progressbar runs in the same backgroundWorker component thread or it runs in the default application thread (UI Thread ) ? - This question for more understanding , as I'm not familiar with Multi-Threading operations .
Was This Post Helpful? 0
  • +
  • -

#8 Renagado  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 117
  • View blog
  • Posts: 388
  • Joined: 14-June 09

Posted 11 November 2009 - 02:45 AM

It's being drawn etc from the UI thread, the backgroundworker's thread basically calculates the value it needs to be and sends it through the ProgressChanged event to the UI thread, where it it updated by the UI thread.

So in other word the 2nd(backgroundworker) thread will never directly touch any of the controls made on your UI thread, you can try it if you like, and you'll see it gives you an error.
Was This Post Helpful? 0
  • +
  • -

#9 Basoom  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 11-November 09

Posted 15 November 2009 - 02:06 AM

Thanks a lot . Your reply was very helpful & Thanks again for your efforts
Was This Post Helpful? 0
  • +
  • -

#10 dmatsumoto  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 04-December 09

Posted 04 December 2009 - 11:23 AM

I happened to find this article today, and the last 3 lines are exactly what I am doing -- I am using the UserState variable to pass information on the fly. However, I'm doing something weird:

- app starts worker thread
- worker thread launches a workflow
- app passes BackgroundWorker to thread, which passes it to the workflow

now the workflow can update the GUI by passing information via the UserState. In order to keep the BackgroundWorker running, I've had to use a hack and I just have the DoWork event handler spin forever in a loop that sleeps. But now I have no way to stop it. Can you recommend anything?

Is there a better way to do on the fly, one-way updates from a worker thread than to use the BackgroundWorker?
Was This Post Helpful? 0
  • +
  • -

#11 Renagado  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 117
  • View blog
  • Posts: 388
  • Joined: 14-June 09

Posted 04 December 2009 - 12:29 PM

My first thought is to make a global bool variable and check for that in your loop in the event handler. But I think there must be a smarter way. Maybe you could post some of the code in the c# forum, that's a better place fot that.
Was This Post Helpful? 0
  • +
  • -

#12 youshi  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 07-December 09

Posted 08 December 2009 - 12:39 AM

Thanks very much... it was a great help. One day I hope I can also be as helpful to a fellow programmer. :^:
Was This Post Helpful? 0
  • +
  • -

#13 htvnormann  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 20-November 09

Posted 11 January 2010 - 12:30 PM

This is exactly what I needed. :^:
Was This Post Helpful? 0
  • +
  • -

#14 Guest_Joe Coder*


Reputation:

Posted 25 March 2010 - 09:05 PM

Awesome, I love it. Great job!!!!
Was This Post Helpful? 0

#15 tlhIn`toq  Icon User is offline

  • Please show what you have already tried when asking a question.
  • member icon

Reputation: 5674
  • View blog
  • Posts: 12,186
  • Joined: 02-June 10

Posted 06 June 2010 - 11:51 AM

Has anyone used this on VS10 in Win7x64?
It sure seems like it doesn't behave correctly. Either that or under the most simple of test conditions it gets weird.

Brand new WinForm project. No controls, just a BackgroundWorker on Form1 that starts when the form loads.

The BGW has a progress event that writes to the Console. And a completed event that also writes to the console.
This could not be much simpler for a test.
  • App launch
  • Form1 load
  • Worker thread counts to 100,000 in the background, updating on the Console.
  • Worker thread says "Complete" when it is done.


Yet while it is running (and you can see the % climbing in the Console)... Form1 reports (not responding) and the cursor changes to busy.
The whole idea is the application shouldn't show as not responding and the GUI shouldn't be hung up.
Am I missing something very simple here?
It produces the same 'hung up' behavior with or without the Application.DoEvents() line. I was just grasping at staws there.

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int bob = 0; bob < 100000; bob++)
            {
                backgroundWorker1.ReportProgress(bob/100);
                Application.DoEvents();
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Console.WriteLine(e.ProgressPercentage.ToString() + " %");
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Console.WriteLine("Thread Complete");
        }

    }


The designer has not been modified. It filled the event handlers from a double-click in the designer's Properties|Events pallet
namespace bgwTest
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
            this.SuspendLayout();
            // 
            // backgroundWorker1
            // 
            this.backgroundWorker1.WorkerReportsProgress = true;
            this.backgroundWorker1.WorkerSupportsCancellation = true;
            this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
            this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged);
            this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 262);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);

        }

        #endregion

        private System.ComponentModel.BackgroundWorker backgroundWorker1;
    }
}



Was This Post Helpful? 0
  • +
  • -

  • (2 Pages)
  • +
  • 1
  • 2