Click here to Skip to main content
Email Password   helpLost your password?

Introduction

This article tackles two problems. First, I have a long task I want to run in the background, but the user should be able to start using the Windows form immediately, even though the task is not done. I need the results of that background thread to be available for use in the original form. Second, I need the original form to know when the background thread is finished, and make changes to itself when that happens. I can do this with an event, but, the event handler in the original form can not update the form directly, because the .NET framework considers that event to be part of the background thread. I have to use BeginInvoke() in the original form to do GUI updates.

I am largely self taught, and learn a lot here on CodeProject. There are many articles on threading, but I didn't find any that gave a step by step process, direct and straight to the point. This article assumes that you know how to write a program in C#, have a clue as to what Threading is, and just want to know how to implement it in a C# Windows form.

I find the logic behind running a process in a background thread to be easier to grasp when the background thread is in a separate class, so I always implement it that way. If you need to set up your background thread in the same class as the calling thread, you can probably find another article that comes closer to showing what you need. [Update--See the Update section for an all in one class solution which I now like better.]

Background

In this demo project, I launch the background thread from a button click on the form to make it easy to see how things work together; in the real project which inspired this article, I launch it from Form.Load(). In the demo, I am filling a generic List<> with strings (and using Thread.Sleep(2000) to simulate a two second database query); in the real project, the List<> gets filled with business objects which are instantiated from a database query.

In the demo, the GUI update is just updating a label on the form; in the real project, I am holding the business objects in a generic List<> I can search (on the user's request), and the GUI update is adding a subset of those objects to the ListBox as initial suggestions. (Or, I might want to start with my Search button disabled, and enable it when the list is ready to search.) In the demo, I have a Join Thread button to demonstrate what happens if I try to use the list before the background thread has finished making it; in the real project, joining the thread is the first thing I do when the user attempts to search the list.

Running the demo

Run the program and click the "Start Thread" button. A label indicates that we have joined the thread. If you wait a couple of seconds, the label will indicate that the thread is done. When you click the "Join Thread" button, the label indicates that we are joining the thread.

If you click "Join Thread" before the thread is finished, the program will wait for the thread to finish, then the label will indicate that the thread is done and fill the ListBox. If you join the thread after it is done, the ListBox will fill, but the label will not change from "Joining Thread". I did this to demonstrate that we can join the thread whether it is finished or not. "Clear List" and "Join Thread" again to see that we can join the thread repeatedly.

Note: If you click "Start Thread", "Join Thread", and "Start Thread" again in rapid succession, the .NET framework will buffer the second "Start Thread" click, and execute it after the thread is finished. A method of avoiding this is explained below.

Using the code

Create a new Windows form, import System.Threading so we don't have to specify it all the time, and add a class for our background thread.

using System.Threading;

public partial class MainThreadForm : Form
{
   //...Form code goes here...
}

public class BackgroundWorkThreadClass
{
   //...Worker class coge goes here...
}

Next, add the object which the form wants the background object to work on; in this example, a generic List<> of strings. Note that we construct a new List<> in the main form, but not in the background thread class. Our background class takes the already instantiated List<> as an argument to its constructor. Let's also add the work in the background thread class, DoLongTask(), and, in the form class, we'll instantiate the background class and its Thread and start the Thread.

Note that when we instantiate the Thread, we pass it a new ThreadStart, and we feed the ThreadStart bwt.DoLongTask and not bwt.DoLongTask(), as we would do if we were calling DoLongTask() directly. Also, remember that we would normally start the Thread in MainThreadForm_Load instead of a button click.

public partial class MainThreadForm : Form
{
    private List<string> ResultList = new List<string>();
    private Thread bgWork;

    private void btnStart_Click(object sender, EventArgs e)
    {
    BackgroundWorkThreadClass bwt = 
            new BackgroundWorkThreadClass(ResultList);
    bgWork = new Thread(new ThreadStart(bwt.DoLongTask));
    bgWork.Start();
    lblStstus.Text = "Thread started";
    }
}

public class BackgroundWorkThreadClass
{
    private List<string> ResultList;

    public BackgroundWorkThreadClass(List<string> rList)
    {
        resultList = rList;
    }

    public void DoLongTask()
    {
        //Fill the list with data
        for (int x = 65; x <= 90; x++)
        {
            char c = (char)x;
            resultList.Add(c.ToString() + " = " 
                 + (x - 64).ToString());
        }
        //Simulate a 2 second long database query
        Thread.Sleep(2000);
    }
}

At this point, we have enough code to do the work. The button will launch the Thread, and when the Thread is done, our List<> back in the form will be populated with our strings. Now, we want to be able to use that List<>, but wait patiently if it isn't ready. Let's add code for the "Join Thread" button. Note that we want to do a GUI update before we join the Thread, but it won't run until the button click handler is done, and the handler won't finish until the thread does; so, we use Application.DoEvents() to force the GUI update immediately. (In a real project, we might want to do something like MainThreadForm.Enabled = false for our first line here, and then MainThreadForm.Enabled = true at the end of the handler to stop our impatient users from clicking everything in sight while they wait.)

private void btnJoin_Click(object sender, EventArgs e)
{
    lblStstus.Text = "Joining Thread";
    Application.DoEvents();
    listResults.Items.Clear();
    bgWork.Join();
    listResults.Items.AddRange(ResultList.ToArray());
}

Now, we have done work in a background thread and used the results in the main thread safely, but we also want to automatically do some work in the GUI as soon as the task is done. We need to implement an event. This will require code in the form, the background work class, and in between the two. Between the form class and the worker class, add a delegate for the events and handlers to register with. We have to give the delegate EventArgs. We could also use a sender object, and we could use a custom EventArgs if we wanted to pass some information along with the event, but in this case, all we care about is being told when the work is done, so plain vanilla EventArgs will work just fine.

public partial class MainThreadForm : Form
{
    ...Form code goes here...
}

public delegate void BgWorkDoneHandler(EventArgs e);

public class BackgroundWorkThreadClass
{
    ...Worker class coge goes here...
}

Now, the worker thread class needs an event. At the top of the class where we declared the private List<string> ResultList;, we will add this code:

public event BgWorkDoneHandler OnWorkDone;

Then, the worker thread class needs to raise the event when the work is done, so we'll add this code to the DoLongTask() method.

public void DoLongTask()
{
    //Fill the list with data as above, code deleted here for brevity
    ...
    //Raise the event to signal that the work is done
    if (OnWorkDone != null)
    {
        OnWorkDone(new EventArgs());
    }
}

Now, all that remains is for the form to register the event and respond to it. Back in the button handler where we run the background Thread, insert a new line between instantiating the background class and instantiating the Thread (I'll repeat those lines here). On the new line, type the start of the line shown here...

BackgroundWorkThreadClass bwt = new BackgroundWorkThreadClass(ResultList);
bwt.OnWorkDone += bgWork = new Thread(new ThreadStart(bwt.DoLongTask));

After you type the "=" sign, Visual Studio will smartly show something like this:

Sample Image - maximum width is 600 pixels

Hit the Tab key, and it will switch to something like this:

Sample Image - maximum width is 600 pixels

Hit the Tab once more, and you get all of this (not guaranteed to be in this order, but it will be there):

private void btnStart_Click(object sender, EventArgs e)
{
    BackgroundWorkThreadClass bwt = new BackgroundWorkThreadClass(ResultList);
    bwt.OnWorkDone += new BgWorkDoneHandler(bwt_OnWorkDone);
    bgWork = new Thread(new ThreadStart(bwt.DoLongTask));
    bgWork.Start();
    lblStstus.Text = "Thread started";
}

void bwt_OnWorkDone(EventArgs e)
{
    throw new Exception("The method is not implemented.");
}

Notice again that we (or VS, in this case) omitted the () when passing the bwt_OnWorkDone() method to the handler constructor. Now, obviously, we need to do something in the event handler besides throwing an exception. If we want to do something internal, we can do it right here in the event handler. Say, we have made a boolean field at the top of the class and called it ListIsReady. We can set ListIsReady = true right here, and it is considered thread safe. But, we want to update the GUI, so we need to have a separate method and use BeginInvoke() on it, and to do that, we have to declare a delegate. Back at the top of the form class where we declared our List<> and our Thread, we need to add this:

private delegate void UpdFromEventDelegate();

Then, we can add our GUI update in a new method, and invoke that method through the delegate, once again skipping the ().

void bwt_OnWorkDone(EventArgs e)
{
    //Non-GUI code is safe to run right here
    BeginInvoke(new UpdFromEventDelegate(UpdFromEventMethod));
}

private void UpdFromEventMethod()
{
    lblStstus.Text = "Thread done";
}

And, there you have it. The form creates a background worker, launches it, gets notified when the work is complete, and updates its own GUI when it gets that notification.

Points of interest

I wrote this article in self defense. I don't need to do this very often, and always forget just how to do it. I never like the way anyone explains it, so I end up reading several articles to remember how. Hopefully, you will find this step by step presentation to be helpful. I know I will, next time I need to do it and forgot how again.

I learned that when you write an article for CP, you may just have a better way suggested to you...see Update.

Update

Thanks to a good comment from PIEBALDconsult that made me think, here is how it works all in one class. I actually like this better now that I know I can check InvokeRequired and then Invoke a method from within itself. Note that as above, we omit the () from the method name we reference in new ThreadStart(bgTask) and new MethodInvoker(setLabel).

//Create and launch the thread
private void Form2_Load(object sender, EventArgs e)
{
    Thread t = new Thread(new ThreadStart(bgTask));
    t.Start();
}
//This is the method that runs in the background
//It calls a method at the end which updates the GUI
private void bgTask()
{
    Thread.Sleep(5000);
    setLabel();
}
//The GUI update method Invokes itself if needed
private void setLabel()
{
    if (this.InvokeRequired)
    {
        this.Invoke(new MethodInvoker(setLabel));
    }
    else
    {
        label2.Text = "done";
    }
}
You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
QuestionBackGroundWorker
marco_br
7:52 3 Jun '08  
First of all, congratulations! Your article is very well written!

But, just a doubt: how do you compare your solution to using the BackgroundWorker class available in .NET 2.0?

Because, from my point of view, your solution seems quite similar to the way BackgroundWorker works. And this class still allows you to report the progress of the background thread (to update a progressbar, for example).

Anyway, you got a 5 from me.
AnswerRe: BackGroundWorker
AWSCodeGuy
8:33 3 Jun '08  
marco_br wrote:
how do you compare your solution to using the BackgroundWorker class available in .NET 2.0?


Well, first of all, I wasn't familiar with that class, so I don't compare it authoritatively. Sniff
I will say that from what looking I did, the progress report would not work for my particular case since the bulk of the time in my background task is a database query. If I had a task where, say, I gave it a long list of objects and asked it to perform a complex operation on each one, then the background task could report how many objects it had processed. A long query, onthe other hand is either done or not, there is no way to measure how far along we are. Same goes for cancelling the background task. We can't interrupt the query, where with the long list of objects we could stop after the next object.
Or, at least, that's what I just read...
Now, as I'm writing this, I'm thinking that maybe if I used a SqlDataReader I could check for cancels and report progress every 10 rows or something...

I smell another article... Big Grin
GeneralRe: BackGroundWorker
PIEBALDconsult
19:15 3 Jun '08  
AWSCodeGuy wrote:
can't interrupt the query


Have you tried Command.Cancel() ? (I haven't.)


AWSCodeGuy wrote:
used a SqlDataReader


That's what I do. The query itself may still take a while to complete, but I can read through the DataReader logging every so many records and/or so many seconds, so I can see that it's doing something.


I rarely use a DataAdapter to Fill a DataTable; I don't like flying blind.
GeneralRe: BackGroundWorker
marco_br
3:28 4 Jun '08  
PIEBALDconsult wrote:
Have you tried Command.Cancel() ? (I haven't.)


I haven't too, but, to be honest, up to now in my application I don't have a query that takes much long. Anyway, thanks for the heads up. It might be useful in the future... Cool


PIEBALDconsult wrote:
That's what I do. The query itself may still take a while to complete, but I can read through the DataReader logging every so many records and/or so many seconds, so I can see that it's doing something.


Would you mind to post an example (code snippet) of how do you do that?


Regards
GeneralRe: BackGroundWorker
PIEBALDconsult
6:50 4 Jun '08  
How I do that? A simple snippet wouldn't suffice, I have a rather large system of classes for doing database access.

The main piece for doing the checkpointing is a class named DatabaseStatistics.
DatabaseStatistics keeps counts of records processed, duplicates encountered, and relational integrity violations encountered.
(Generally I don't stop processing just because of a problem with one record, I log it and keep giong.)

After a DataReader.Read, or as I foreach a DataTable, I can call this.statistics.Increment ( DatabaseStatistics.CountOf.Lines ) ;

When I catch a PIEBALD.Data.DuplicateException, I can call this.Statistics.Increment ( PIEBALD.Data.DatabaseStatistics.CountOf.Duplicates ) ;

When I catch a PIEBALD.Data.UnmatchedException, I can call this.Statistics.Increment ( PIEBALD.Data.DatabaseStatistics.CountOf.Unmatcheds ) ;


The DatabaseStatistics can optionally be set to perform checkpointing.
To enable checkpointing, you provide a number of records and/or a timespan.
Each time the CountOf.Lines is incremented, a check is made to see whether or not a checkpoint is due.
If so, the OnCheckpoint event is raised, which includes a
new PIEBALD.Types.Event.CheckpointEventArgs ( this [ CountOf.Lines ] , this.Elapsed )

The calling application may attach a handler for that event, I attach my logging mechanism
db.OnCheckpoint += new PIEBALD.Types.Event.CheckpointEvent ( LogMessage ) ;

The log will then contain rows like
<Entry Time="2008-06-02T14:17:27-07:00" Facility="Porthole" Function="TTI.BillOfFare.Service.srvPortHole.DoPortalStuff(PortalAccessor dbc)">Processed 8 rows in 2 seconds (4 rows per second)</Entry>
GeneralRe: BackGroundWorker
marco_br
8:05 4 Jun '08  
Thanks for the explanation, but, actually, now I noticed that you do your logging after the query is executed. When I first read your message, I understood you did it in the middle of the query execution.

Anyway, your system is really interesting. Thanks again for sharing.
GeneralRe: BackGroundWorker
PIEBALDconsult
8:21 4 Jun '08  
Wish I could, but then again, what would it report?
AnswerRe: BackGroundWorker
AWSCodeGuy
8:22 4 Jun '08  
I have not tested this (obviously), but it builds ok and should give you the general idea.
            // Just a place to put results for demo
List<string> theData = new List<string>();
// This connection string won't work ... LOL
SqlConnection conn =
new SqlConnection("your connection string");

//Get the count of records available
string countSQL = "Select Count(*) from Customers";
SqlCommand countCmd = new SqlCommand(countSQL, conn);
int recCount = (int)countCmd.ExecuteScalar();

//Set up the SqlDataReader
string recordsSQL = "Select * from Customers";
SqlCommand recordCmd = new SqlCommand(recordsSQL, conn);
SqlDataReader dr = recordCmd.ExecuteReader();

// Variables for tracking progress
int recsRead = 0;
int myProgress = 0;
while (dr.Read())
{
string custName = (string)dr["username"];
int custID = (int)dr["userid"];
theData.Add(custName + "=" + custID.ToString());
if ((int)(recCount / recsRead) > myProgress)
{
myProgress = (int)(recCount / recsRead);
bgw1.ReportProgress(myProgress);
}
}
dr.Close();

Does that help?
AnswerRe: BackGroundWorker
marco_br
8:48 5 Jun '08  
In fact, that's exactly the kind of stuff I do.
Thank you very much, anyway, for the response.
Cool
GeneralRe: BackGroundWorker
AWSCodeGuy
3:31 4 Jun '08  
PIEBALDconsult wrote:
can't interrupt the query


Have you tried Command.Cancel() ? (I haven't.)


Haven't tried it but did some quick reading. Apparently the BackgroundWorker can only respond to the .Cancel() between steps in it's procedure. If the procedure the BackgroundWorker is running only has a few steps, like
string commandstring = "select * from PhoneNumbers";
myAdapter = new SqlDataAdapter(commandstring, connectionString);
myAdapter.Fill(myTable)
then it only has two chances to be interrupted. Assuming that the procedure takes ten seconds, the last setp is taking 9.9 something of those seconds. With a SqlDataReader on the other hand, we could read the rows in a loop and have an opportunity to cancel after every row.
GeneralRe: BackGroundWorker
marco_br
3:19 4 Jun '08  
Actually, I'm not sure if what I do is the best way, but, in my application, I report progress not of the query itself, but of the iterations throughout the DataReader results as they fill in my Value Objects. At least this gives the user an idea of the progress for queries that are simple, but bring a considerable bunch of results.

And also, I use the CancelAsync() method and check for the Cancelled property of the RunWorkerCompletedEventArgs object to enable the user the cancel these operations.
GeneralRe: BackGroundWorker
AWSCodeGuy
3:40 4 Jun '08  
marco_br wrote:
I report progress not of the query itself, but of the iterations throughout the DataReader results as they fill in my Value Objects


Exactly...that was what I meant, although I haven't tried it yet.
I also thought that you could report an exact percent of completion by first getting the number of rows with
Select Count(*) from customers Where custName = 'Smith'
and then when are reading all the actual rows for customers named Smith and we check our progress we know we have pulled 50 rows out of 1000, not just that we pulled 50 rows.
AnswerRe: BackGroundWorker
marco_br
5:06 4 Jun '08  
AWSCodeGuy wrote:
I also thought that you could report an exact percent of completion by first getting the number of rows with
Select Count(*) from customers Where custName = 'Smith'
and then when are reading all the actual rows for customers named Smith and we check our progress we know we have pulled 50 rows out of 1000, not just that we pulled 50 rows



Correct. I do that! Big Grin
GeneralGood Tutorial
merlin981
5:39 2 Jun '08  
Good job on a very easy to follow tutorial on the beginnings of threading. I'm sure many people will find it helpful. 5 from me



~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LINQ Exchange - Learn about LINQ and Lambda Expressions
Rhabot - World of Warcraft Bot
Make long URLs short with NeatURL.net
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

GeneralWell, that's one way...
PIEBALDconsult
18:27 31 May '08  
But I don't see a need for a separate class and an event.

When I do that I simply spin off a Thread to run one of the methods in the form class. (Often it's a Timer_Elapsed method.)

If it needs to update the UI, then I write a method to do that, but it checks InvokeRequired and invokes itself when true, that way the main Thread can use the same method without invoking.
GeneralRe: Well, that's one way...
supercat9
10:31 1 Jun '08  
What's the best way for the main thread to act 'busy' (don't acknowledge button pushes, etc.) and yet still be able to handle display updates, method invocations, etc.? Opening up a modal dialog would be one approach, but having such dialogs appearing all the time could be distracting.

It's unfortunate that .net doesn't seem to offer any nice way to, e.g., wait up to X duration for a semaphore, but handle any method invocations that arrive in the meantime.
GeneralRe: Well, that's one way...
PIEBALDconsult
18:37 1 Jun '08  
If the form isn't very complicated I set the controls' .Enabled=false while I do other things.
GeneralRe: Well, that's one way...
supercat9
19:03 1 Jun '08  
PIEBALDconsult wrote:
If the form isn't very complicated I set the controls' .Enabled=false while I do other things.


Doesn't that change the appearance? I suppose that if an action is going to take a long time, it might be functionally and aesthetically reasonable to gray out the controls while waiting for the action to complete, but if an action will typically take a quarter-second or less, it would seem to simply be a distraction.
GeneralRe: Well, that's one way...
PIEBALDconsult
20:08 1 Jun '08  
supercat9 wrote:
Doesn't that change the appearance?


Yes, as it should. The user should have be notified that the control won't work.


supercat9 wrote:
if an action will typically take a quarter-second or less


Then why spin off a thread in the first place?


Otherwise, I suppose your form class could have a semaphore and each event handler could check it.
But again, if a control isn't greyed-out the user has a reasonable expectation that the control will work.
GeneralRe: Well, that's one way...
supercat9
17:44 2 Jun '08  
PIEBALDconsult wrote:
Yes, as it should. The user should have be notified that the control won't work.


I would think that a cleaner approach would be to have the button that was pushed shift from being depressed/enabled to depressed/disabled, while other buttons remain unmodified and a progress-bar indicator on the bottom of the window shows what's going on.

Such an approach would avoid visual distraction in the cases where the action completed within 100ms, but would still provide clear user feedback in the cases where the action took ten seconds.

Actions which are expected to take a long time should probably visibly disable all the buttons and/or pop up a modal progress indicator. In some cases, however, a user will perform actions which may take a highly variable length of time. Approaches which would be suitable for actions which were known to be instantaneous or known to be long might not always be suitable for actions whose length is unknown.
GeneralRe: Well, that's one way...
PIEBALDconsult
18:31 2 Jun '08  
Hmmm... now I'm thinking about buttons on some juke boxes, which stay depressed while something happens then pop back out.

That could be interesting, but it only affects that button (it can't be pressed again while it's in the depressed state).
GeneralRe: Well, that's one way...
supercat9
6:07 3 Jun '08  
PIEBALDconsult wrote:

That could be interesting, but it only affects that button (it can't be pressed again while it's in the depressed state).


Visually, it only changes the button that's pushed. However, I would expect that as on a jukebox, any other buttons within the functional group would also be disabled until the button pops out again.

BTW, weren't those old juke box terminals neat? I'm surprised I've never seen restaurants with a modern digital version, especially given that a licensed music server should be able to supply music to different tables independently.
AnswerRe: Well, that's one way...
AWSCodeGuy
3:33 2 Jun '08  
In the context of this article the suggestion for .Enabled = False goes like this:
The user opens the form which immediately starts doing a background thread which prepares a large list of data for use in lists in the form. The process takes 10 to 20 seconds, during which I want the user to be able to do other things on the form, but if he tries to search the data from the background task before the bg task is done, I have a problem.
So, I can either disable the search button until the data is ready, or disable the form when the search button is clicked, join the thread, and enable the form again when the thread completes. I prefer the former solution, hence the need to update the GUI (enable the button) on completion of the thread.
Does that make more sense?
GeneralRe: Well, that's one way...
PIEBALDconsult
6:39 2 Jun '08  
AWSCodeGuy wrote:
So, I can either disable the search button until the data is ready


AWSCodeGuy wrote:
I prefer the former solution


Yes
GeneralRe: Well, that's one way...
AWSCodeGuy
3:20 2 Jun '08  
Invoke itself...never thought of that. As I said, the seperate class just makes more sense to me. I find it easier to maintain if I have to re-visit it later. Isn't that just a matter of personal style?


Last Updated 31 May 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010