INTRODUCTION
This little article just tries to point out some important features (from my view) about how BackgroundWorker
class works. More in depth, I would like to show what type of threads are executing each piece of code contained in event handlers being consumed by BackgroundWorker
objects. If you don't know what a thread is, I strongly recommend that you read a little more about them at MSDN or in any other article like here.
BACKGROUND
BackgroundWorker
class is contained in System.ComponentModel
namespace and it is used for executing tasks on other separated and dedicated worker threads different from the main one. So, you can execute in the background time-consuming or heavy operations whereas the user interface (UI) remains completely available to interact with the end user.
For instance, you may think of using this component to deal with heavy downloads, long delays owing to database operations, transactions, etc. while avoiding the awful issue of having to put up with a user interface that seems as though it has stopped responding.
Although the class lacks some features that you can implement by directly using all the available functionality of threads, it is very straighforward to use and will solve the most part of simple cases that you might come across in your daily programming tasks.
BackgroundWorker
can raise four different types of events (more information at BackgroundWorker Class Microsoft Documentation). We'll put the focus on DoWork
, ProgressChanged
and RunWorkerCompleted
events, firstly giving a brief explanation and secondly, by checking with a very basic application which type of thread is executing the events handlers related to them. Here are the events:
Occurs when the component is disposed.
Occurs when BackgroundWorker
is started by calling RunWorkerAsync
method. Handlers for this event should never try to update the user interface (henceforth the UI) because they will be executed on other secondary threads different from the thread that created the BackgroundWorker
object.
Occurs while BackgroundWorker
is executing some task and ReportProgress
method is called.
The call to the ReportProgress
method is asynchronous and returns immediately. This way, worker threads executing code in the background make a call to ReportProgress
method and then, the thread that created the BackgroundWorker
object is invoked to handle ProgressChanged
event.
Here is where you have to place your code to update UI elements bearing in mind that BackgroundWorker
object should have been created by the main thread. Then, we'll fulfill the basic requirement that elements in UI must be updated only by the main thread. Otherwise, problems will appear in your application without any doubt.
Finally, the WorkerReportsProgress
property value must be true, or ReportProgress
will throw an InvalidOperationException
.
Occurs when BackgroundWorker
has finished its task, has been cancelled or something has gone wrong and an exception has been raised.
USING THE CODE
As it is said in Microsoft Documentation, "to set up for a background operation, add an event handler for the DoWork
event. Call your time-consuming operation in this event handler. To start the operation, call RunWorkerAsync
. To receive notifications of progress updates, handle the ProgressChanged
event. To receive a notification when the operation is completed, handle RunWorkerCompleted
event".
Let's pretend that the initialization of our application is time-consuming and for this reason, we want to show a "Splash Window" with a progress bar before showing the "Main Window". We might write some code similar to the one below:
SplashWindow splashWindow;
MainWindow mainWindow;
private void App_Startup(object sender, StartupEventArgs e)
{
// Creates and shows the Splash Window.
splashWindow = new SplashWindow();
splashWindow.Show();
// Creates Main Window without showing it. Waits for complete all remaining tasks.
mainWindow = new MainWindow();
// Initialize application components using a background worker.
// Background Worker is created on main thread.
// It's used to get multi-threading.
System.ComponentModel.BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
As you can see above, "Splash Window" is showed immediately while "Main Window" is created but not showed. Just after this, a BackgroundWorker
object is created to execute a piece of code from worker_DoWork
handler, whose progression will be handled by worker_ProgressChanged
handler. Once the task is finished, worker_RunWorkerCompleted
handler will be called to close "Splash Window" and show "Main Window".
Here is a picture showing how the "Splash Window" could be like:
Here is the code related to handlers:
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
/* It executes on the thread that created the BackgroundWorker */
splashWindow.UpdateProgress(e.ProgressPercentage);
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
/* Simulates a heavy task. It is executed on a worker (not main!) thread. */
for (int i = 0; i <= 10; i++)
{
/* The call to the ReportProgress method is asynchronous and returns immediately */
/* It will trigger ProgressChanged event */
(sender as BackgroundWorker).ReportProgress(i * 10, i);
Thread.Sleep(500);
}
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Closes splash window
splashWindow.Close();
// Shows main window
mainWindow.Show();
}
The code above is very straightforward and easy to understand as I explained before.
- A progress bar from the User Interface in "Splash Window" will be updated for showing initialization progression by means of code executed in
worker_ProgressChanged
handler.
Here is a screen shot with "Threads Debug Window" in Visual Studio:
You can see that it's executed on the Main Thread (Id=6160, the same that created the BackgroundWorker). It's highlighted in the Debug Thread Window and indicated with a yellow arrow.
- A time-consuming task will be simulated and executed in
worker_DoWork
handler, calling ReportProgress method to cause an invocation of worker_ProgressChanged
handler.
Here is a screen shot with "Threads Debug Window" in Visual Studio:
You can see that it's executed on a secondary dedicated new thread with Id=4732
- Finally,
worker_RunWorkerCompleted
handler will be called when worker_DoWork
handler finishes their work.
KEY POINTS AND CONCLUSION
At least, remember these important two key points:
- Time-consuming operations should be placed in
DoWork
event handlers. Code will be executed on dedicated and separated new worker threads that never should try update the user interface.
- The call to the
ReportProgress
method is asynchronous and returns immediately. The ProgressChanged
event handler executes on the thread that created the BackgroundWorker
. Here is where you have to put your code to update the UI.
- The class lacks some advances features but it might be enough for solving the most part of basic requirements you will come across.