Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

BackgroundWorker.ReportProgress is Asynchronous

4.92/5 (4 votes)
1 May 2009CPOL3 min read 45K  
BackgroundWorker.ReportProgress is Asynchronous

I never noticed this before but the BackgroundWorker.ReportProgress method returns before the control’s ProgressChanged event has completed. It may return before the ProgressChanged event has even started!

For those not familiar with the BackgroundWorker control, this control simplifies creating a worker thread, especially for the purpose of keeping the user interface responsive while the worker thread performs a lengthy process.

One issue it simplifies relates to the fact that the worker thread cannot directly access the form or its controls because those objects were created by the UI thread. Instead, code running in the worker thread can call the control’s ReportProgress method, which raises the control’s ProgressChanged event. You can pass information to ReportProgress that describes the current state of the lengthy process, and the handler for the ProgressChanged can use that data to display it to the user in your form controls.

I had been using this approach for a lengthy operation that could run for days. A lot was going on so I was passing an instance of a custom class that contained various bits of progress information. But, at one point, I saw that the progress information being displayed was not correct. On further inspection, I could see that my worker thread was updating the progress information object before the ProgressChanged event handler had a chance to display that information.

It is very easy to get caught up with multi-threading issues as some things are just not very intuitive. When I called the ReportProgress method, I had just assumed that it would not return until the ProgressChanged event had completed. But I was wrong.

Thinking about it, the way this control works makes sense. If, instead, the worker thread was blocked until the event had returned, some of the benefits of a worker thread would be lost as one thread would be shut down during that time. Also, note that the ProgressChanged method is overloaded. One version simply takes an integer argument. Since integers are passed by value, there would be no reason to suspend the worker thread when using this version of the ProgressChanged method.

The other version takes an object in addition to the integer argument. That’s the version I was using. Since class objects are passed by reference, changes to this data made in the worker thread would be reflected in the same object being used in the ProgressChanged event.

At first thought, I wondered if maybe I should resolve this by blocking the worker thread somehow until the event had run to completion. But, as I’ve already pointed out, this eliminates some of the advantage of having a worker thread in the first place. A much simpler solution is to simply make a copy of my progress class object. This way, the worker thread can modify its copy as needed while the ProgressChanged event is reading its copy, perhaps both at the same time.

Note that I only required a “shallow” copy. In the case of value members, a shallow copy will create a true copy of those members. In the case of reference members, the copy is actually a reference to the same object. The only reference members in my case were strings. Since strings are immutable and cannot be changed, if my code updated one of these members, that would create a new string and not affect the original one referenced in the object passed to ReportProgress.

C#
protected class ProgressInfo
{
    public int current;
    public int total;
    public string message;
    public object Clone()
    {
        return (ImportStatus)MemberwiseClone();
    }
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    ProgressInfo info = new progressInfo();
    info.total = 1000;

    for (info.current = 0; info.current < info.total; info.current++)
    {
        info.message = String.Format("Processing item {0}",
            info.current + 1);
        backgroundWorker1.ReportProgress(0, info.Clone());
        //
        // Further processing on this item
        //
    }
}
Listing 1: Passing copy of object to BackgroundWorker.ReportProgress

Listing 1 shows some sample code. The ProgressInfo class declares a Clone() method, which calls MemberwiseClone(). MemberwiseClone() performs a shallow copy of the object. Note that this method is protected and, therefore, can only be called from a method of the class (or a derived class). This is why it was necessary to create the additional, public, “wrapper” method in my class, which my worker thread can call.

Using this code, my ProgressChanged event handler can take its time displaying the progress data and will not be affected by my background worker thread making changes to its copy of that data at the same time.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)