Inside W3Hearts
Categories
Windows Programming
C#
Visual Basic 6.0 / .NET
C / C++ / Visual C++ (MFC , SDK)
Packaging & Deployment
Web Programming
ASP.NET
ASP.NET Webservices
PHP & MySQL
J2EE / Servlet / Struts
Client side coding
Javascript & AJAX
CSS
HTML / XHTML
Database
SQL Server
Oracle
MySQL
 
 Windows Programming >> C#  
 
Threading with BackgroundWorker helper class
by Navaneeth.K.N
Jun 4th 08

You are developing an application which has a long running job. You run this job on the main thread and your GUI got hanged until the job finishes. Obviously you will think about running the process in a parallel thread which reports the work progress to main thread and display progress on the GUI. In this article we will discuss about using a helper class called BackgroundWorker which is included since .NET framework 2.0 to make multi threading more easier.

Introduction to threading

A small piece in execution can be called as a thread. Multithreading can be used to execute parallel tasks along with the other threads. Common example of multi threading is to keep GUI active when a long running process is executing. System.Threading is the namespace where classes related to threading are kept. To start a thread, initialize System.Threading.Thread instance and call it's Start() method. Here is how we create a simple thread.

Note
All the examples has System.Threading and System.ComponentModel namespaces included


Code

class Program
{
        static void Main(string[] args)
        {
            Thread t = new Thread(new ThreadStart(Worker));
            t.Start();
        }

        static void Worker()
        {
            Console.WriteLine("Hello from worker ! Current thread is {0}", Thread.CurrentThread.ManagedThreadId);
        }
}


ThreadStart is a delegate which represents the method to be executed when thread starts. Parameterizedthreadstart delegate can be used to pass any custom objects to the worker method.

Why BackgroundWorker class ?

Creating a new thread and executing is pretty simple job, then why do we need such a helper class ? Consider the following code

Code

public partial class Form1 : Form
{
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Thread t = new Thread(new ThreadStart(this.Worker));
            t.Start();
        }

        private void Worker()
        {
            this.label1.Text = "Hello";
        }
}


Above code attempts to update label1 value from a new thread. This type of operations are not allowed and you will end up with the following error message

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

.NET framework don't allow cross-thread control updation which can be handled by modifying the worker method as mentioned below

Code

delegate void WorkerInvoker();
private void Worker()
{
     if (this.InvokeRequired)
         this.Invoke(new WorkerInvoker(Worker));
     this.label1.Text = "Hello";
}


InvokeRequired returns TRUE, if the current thread is not the main thread who created the controls. Invoke() sends the request to windows message pump for execution. This is a common scenario in a multi threaded application. BackgroundWorker provides set of features to make all these easy.

Features of BackgroundWorker

  • A cancel flag to abort the worker safely.
  • Reporting progress, completion and cancellation.
  • Implements IComponent interface which helps control to add using the Visual Studio IDE.
  • Cross thread exception handling is not allowed in .NET, though, BackgroundWorker provides methods to handle exceptions in the worker method
  • Able to update GUI controls.

Working example

For getting details about members of this class, I suggest you to read this MSDN article.

Here is a working code which uses BackgroundWorker.

Code

namespace BackgroundWorkerSample
{
    class Program
    {
        static BackgroundWorker worker = null;
        static void Main(string[] args)
        {
            worker = new BackgroundWorker();
           
            // Hooking events
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); //Fired when progress changes
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); // Fired when worker completes or cancels
           
            // Setting worker bahaviour
            worker.WorkerReportsProgress = true;
            worker.WorkerSupportsCancellation = true;

            // Doing the work
            worker.RunWorkerAsync();    // Start the work on DoWork event handler

            Console.WriteLine("To cancel, press any key");
            Console.ReadLine();
           
            // Requesting cancelation
            worker.CancelAsync();

            Console.Read();
        }

        static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
                Console.WriteLine("Worker cancelled");
            else if (e.Error != null)
                Console.WriteLine("There is an unhandled exception in the worker execution. Exception : {0}", e.Error.Message);
            else
                Console.WriteLine("Worker completed. Result is {0}", e.Result);
        }

        static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Console.WriteLine("Worker completed {0}% of work", e.ProgressPercentage);
        }

        static void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 1; i <= 100; i = i + 30)
            {
                // Checking we have a cancelation request. If yes, canceling the operation
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
                // Doing some work. Just to simulate, we are sleeping for one sec.
                Thread.Sleep(1000);

                // Reporting progress
                worker.ReportProgress(i);
            }
            e.Result = "Success";   // Result takes an object type. This will be passed through RunWorkerCompletedEventArgs
        }
    }
}


Well, in this example we have done the following steps to make the worker running
  • Instantiated new instance of BackgroundWorker
  • Hooked DoWork, ProgressChanged and RunWorkerCompleted event handlers. ProgressChanged event will be fired when worker.ReportProgress() gets called. RunWorkerCompleted will be fired when the DoWork event handler finishes.
  • Setting worker.WorkerReportsProgress and worker.WorkerSupportsCancellation to "true". First one is needed if worker should report progress. A call to worker.ReportProgress() without setting this value to "true" will throw an exception. worker.WorkerSupportsCancellation should be true when worker needs to be canceled safely before completing it's work.
  • Called worker.RunWorkerAsync() which fires the DoWork event handler.
I guess the rest of the code is self explanatory. When you run the example, it produces the following output.




Note
BackgroundWorker uses a thread in the thread pool. So never call Thread.Sleep() as shown in the above example. I have used sleep here to simulate a long running process. It is advised to use cancellation mechanisms as described above instead of aborting thread like Thread.CurrentThread.Abort() since BackgroundWorker uses a thread pool thread.

How it works internally

If you are not interested in learning how BackgroundWorker works and manages all the above described functionalities, please feel free to skip this section. In this section, I will be explaining what's happening internally.

What happens when BackgroundWorker gets initialized

  • Initializes the WorkerThreadStartDelegate and supplies WorkerThreadStart method.
  • Initializes SendOrPostCallBack delegate instance which will be used when worker finishes.
  • Initializes another instance of SendOrPostCallBack delegate for reporting progress.
SendOrPostCallBack delegate comes in System.Threading namespace. To learn more, read this MSDN article.

Hooking event handlers

BackgroundWorker is inherited from Component base class. When event handlers are hooked, it is added to the events list in the Component class. Here is how DoWork event looks like

Code

public event DoWorkEventHandler DoWork
{
        add
        {
            base.Events.AddHandler(doWorkKey, value);
        }
        remove
        {
            base.Events.RemoveHandler(doWorkKey, value);
        }
}


Event handlers are added to the events list with a key. This key is a readonly object which is initialized by default. This key is used to get the event handlers back from the events list.

RunWorkerAsync()

This is the method which fires DoWork event handler. Following are the steps
  • Checks worker is running, and throws exception if it is running.
  • Sets running status to "true" and CancellationPending to "false".
  • Initializes AsyncOperation instance and calls BeginInvoke() on WorkerThreadStartDelegate instance. BeginInvoke() uses a thread pool thread to do the job.
Remember when BackgroundWorker initializes, it sets WorkerThreadStartDelegate instance to a method called WorkerThreadStart. So the call to BeginInvoke() explained in the final step will execute WorkerThreadStart method.

WorkerThreadStart()

  • Initializes DoWorkEventArgs instance.
  • Calls virtual OnDoWork method with DoWorkEventArgs instance as parameter.
  • If worker finishes the job without cancellation, it sets the Result property.
  • Creates instance of RunWorkerCompletedEventArgs and calls OperationCompleted() method on the AsyncOperation instance with SendOrPostCallBack instance and RunWorkerCompletedEventArgs instance as parameters.
Here is how this method looks like

Code

private void WorkerThreadStart(object argument)
{
        object result = null;
        Exception error = null;
        bool cancelled = false;
        try
        {
            DoWorkEventArgs e = new DoWorkEventArgs(argument);
            this.OnDoWork(e);
            if (e.Cancel)
            {
                cancelled = true;
            }
            else
            {
                result = e.Result;
            }
        }
        catch (Exception exception2)
        {
            error = exception2;
        }
        RunWorkerCompletedEventArgs arg = new RunWorkerCompletedEventArgs(result, error, cancelled);
        this.asyncOperation.PostOperationCompleted(this.operationCompleted, arg);
}


Note

Call to OnDoWork virtual method is wrapped inside a try/catch block which helps worker to handle uncaught exceptions and supply that to worker completed event handler through RunWorkerCompletedEventArgs instance.

OnDoWork()

Code

protected virtual void OnDoWork(DoWorkEventArgs e)
{
    DoWorkEventHandler handler = (DoWorkEventHandler) base.Events[doWorkKey];
    if (handler != null)
    {
        handler(this, e);
    }
}


OnDoWork() event takes the DoWorkEventHandler instance form the base class event list and invoke it. So the event handler hooked by you in your program will get executed.

This method is marked as virtual and can be overridden in another class which is derived from BackgroundWorker. This provides another pattern for using BackgroundWorker which will be discussed later in this article.

ReportProgress()

Code

public void ReportProgress(int percentProgress, object userState)
{
    if (!this.WorkerReportsProgress)
    {
        throw new InvalidOperationException(SR.GetString("BackgroundWorker_WorkerDoesntReportProgress"));
    }
    ProgressChangedEventArgs arg = new ProgressChangedEventArgs(percentProgress, userState);
    if (this.asyncOperation != null)
    {
        this.asyncOperation.Post(this.progressReporter, arg);
    }
    else
    {
        this.progressReporter(arg);
    }
}

  • Checks progress reporting is enabled, if not throws error.
  • Initializes ProgressChangedEventArgs with progress percentage and the user state object.
  • Checks worker is performing asynchronous operation and call Post() on AsyncOperation instance. This queues the request with the supplied SendOrPostCallBack instance to a thread pool thread, which will be posted to windows message pump later. So the progress changed event will be called on the thread in which controls are created. 
For using BackgroundWorker, you don't need to learn about how it works internally. But it's always good to have an idea on what's happening behind the scenes.

Creating custom worker

We have already discussed about using BackgroundWorker class and it's internals. Sometimes we need to create custom workers which does a specific job. BackgroundWorker is not sealed and allows inheritance. In this section we will discuss about creating custom workers by inheriting from BackgroundWorker.

Assume you are developing a product uploading tool for your e-commerce store. This tool should read images and upload it to the remote server. We created ProductUploader class which derives from BackgroundWorker. BackgroundWorker allows sub classes to override OnDoWork(), OnProgressChanged and OnRunWorkerCompleted methods. Here is the code

Code

namespace CustomBackgroundWorker
{
    class Program
    {
        static void Main(string[] args)
        {
            ProductImageUploader uploader = new ProductImageUploader();
            // Preparing images
            uploader.Images.Add("Image1");
            uploader.Images.Add("Image2");
            uploader.Images.Add("Image3");
            uploader.Images.Add("Image4");
            uploader.Images.Add("Image5");
           
            // Start uploading
            uploader.StartUpload();
           
            // Wait for user
            Console.Read();
        }
    }

    class ProductImageUploader : BackgroundWorker
    {
        /// <summary>
        /// Initializes ProductImageUploader
        /// </summary>
        public ProductImageUploader()
        {
            this.WorkerReportsProgress = true;
            this.WorkerSupportsCancellation = true;
        }

        private List<string> _images = new List<string>();
        /// <summary>
        /// Gets / Sets image file names to be uploaded
        /// </summary>
        public List<string> Images
        {
            get { return _images; }
            set { _images = value; }
        }

        public void StartUpload()
        {
            this.RunWorkerAsync();
        }

        #region Overridden methods

        protected override void OnDoWork(DoWorkEventArgs e)
        {
            base.OnDoWork(e);
            int uploaded = 0;
            foreach (string image in Images)
            {
                if (this.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
                // Upload this image.
                Thread.Sleep(1000);
                // Incrementing upload count
                uploaded++;
                double progress = (double)uploaded / Images.Count;
                // Calculating progress percentage and reporting
                this.ReportProgress((int)(progress * 100), image);
            }
        }

        protected override void OnProgressChanged(ProgressChangedEventArgs e)
        {
            base.OnProgressChanged(e);
            Console.WriteLine("Completed {0}%. Uploaded {1}", e.ProgressPercentage, e.UserState);
        }

        protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e)
        {
            base.OnRunWorkerCompleted(e);
            if (e.Cancelled)
                Console.WriteLine("Worker canceled");
            else if (e.Error != null)
                Console.WriteLine("There's an uncaught error. {0}", e.Error.Message);
            else
                Console.WriteLine("Image upload completed.");
        }

        #endregion
    }
}




When this class initializes, we set WorkerReportsProgress and  WorkerSupportsCancellation to "true". Images property represents image names which has to be uploaded. On the overridden OnDoWork, we are doing the upload and reporting progress.

Conclusion

Threading is a complex subject. BackgroundWorker helps to implement multi threading without knowing much about threading and synchronization mechanisms. In this article, we have seen the internals of  BackgroundWorker and two patterns it offers.

That's all about BackgroundWorker. I enjoyed writing this article and I expect you will also find this helpful. If you liked the article, please vote for it and feel free to leave a comment on the article discussion board.

Discussions

Need to discuss about this article ? Click here
 
© 2007 W3hearts.com
Home | Post Article | Start discussion | Join Community| Privacy policy