Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

BackgroundWorker Helper

4.69/5 (20 votes)
17 Jan 2013CPOL2 min read 42.5K   4.4K  
A class that may shorten your time spent with BackgroundWorker, and handles some logics of working with it.

Introduction

The BackgroundWorker class is used to manage work that is done in a background thread and report progress to the UI, avoiding cross-threading problems. Implementing all BackgroundWorker logic is a bit dirty and does take place in your code.

In this article, I present a wrapper class which handles some work you usually do. It's useful for situations when you need execution of a set of independent tasks.

An example might be sending mail to several recipients or processing a set of images.

Using the Code

The BWHelper class aggregates the actions that should be done. It handles BackgroundWorker.DoWork and checks whether the user decided to use parallel or sequential execution. Then it executes all Actions, checking before each execution if cancellation was asked.

The class also computes the percentage done and the probable time left (based on the average execution time of completed tasks).

C#
public class BWHelper
{
    private IEnumerable<Action> toDo;
    private DateTime startTime;
    
    private bool isParallel = false;
    private BackgroundWorker worker;

    private ValueMonitor<int> percentageProgress = new ValueMonitor<int>(0);
    private ValueMonitor<TimeSpan> timeLeft = new ValueMonitor<TimeSpan>(TimeSpan.MaxValue);

    public void SetActionsTodo( IEnumerable<Action> toDoActions)
    {
        toDo = toDoActions;
    }

    public bool IsParallel
    {
        get { return isParallel; }
        set { isParallel = value; }
    }

    public IValueMonitor<TimeSpan> TimeLeft { get { return timeLeft; } }

    public BWHelper(BackgroundWorker aWorker)
    {
        worker = aWorker;
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;
        percentageProgress.ValueChanged += 
                   new ValueChangedDelegate<int>(percentageProgress_ValueChanged);

        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    }

    public BWHelper(IEnumerable<Action> actionsToDo, BackgroundWorker aWorker):this(aWorker)
    {
        toDo = actionsToDo;
    }

    public int Total
    {
        get
        {
            if (toDo == null) return 0;
            return toDo.Count();
        }
    }

    private void percentageProgress_ValueChanged(int oldValue, int newValue)
    {
        worker.ReportProgress(newValue);
    }

    private void  worker_DoWork(object sender, DoWorkEventArgs e)
    {
        if (toDo == null)
            throw new InvalidOperationException("You must provide actions to execute");
        int total = toDo.Count();
        startTime = DateTime.Now;
        int current = 0;
        if (isParallel == false)
        {
            foreach (var next in toDo)
            {
                next();
                current++;
                if (worker.CancellationPending == true) return;
                percentageProgress.Value = (int)((double)current / (double)total * 100.0);
                double passedMs = (DateTime.Now - startTime).TotalMilliseconds;
                double oneUnitMs = passedMs / current;
                double leftMs = (total - current) * oneUnitMs;
                timeLeft.Value = TimeSpan.FromMilliseconds(leftMs);
            }            
        }
        else
        {
            
            Parallel.For(0, total - 1,
                (index, loopstate) => 
                    { 

                        toDo.ElementAt(index)();
                        if (worker.CancellationPending == true) loopstate.Stop();
                        Interlocked.Increment(ref current);

                        percentageProgress.Value = (int)((double)current / (double)total * 100.0);
                        double passedMs = (DateTime.Now - startTime).TotalMilliseconds;
                        double oneUnitMs = passedMs / current;
                        double leftMs = (total - current) * oneUnitMs;
                        timeLeft.Value = TimeSpan.FromMilliseconds(leftMs);
                    }
                );
        }
    }
} 

The ValueMonitor class is intensively used for int and TimeSpan value types. Note that because TimeLeft change is signaled by means of the ValueMonitor.ValueChanged event rather than doing this using BackgroundWorker, you should implement the cross-threading logic yourself. In the example project, for the sake of simplicity and because I'm lazy, the CheckForIllegelCrossThreadCalls property is set to true.

The example project is a Windows Forms application which executes "useful" tasks, each taking 200 ms. It also shows the possible time left. You also may choose either parallel or sequential execution.

When the Start button is hit, the following code is executed:

C#
List<Action> actions = new List<Action>();
for (int i = 0; i< 100; i++)
    actions.Add( () => Thread.Sleep(200) );

helper.SetActionsTodo(actions);
helper.IsParallel = checkBoxUseParallel.Checked;
backgroundWorker.RunWorkerAsync();

The things we gain from BWHelper are:

  • Percentage counting is concentrated in one place. You also don't have to worry about the ReportProgress method.
  • Average time left is measured. You may handle the BWHelper.TimeLeft.ValueChanged event to show this in the UI.
  • Easy switch between sequential and parallel actions execution.
  • Cancellation logic is also not your task.
  • The properties WorkerReportsProgress and WorkerSupportsCancellation are set to true in the constructor.

While we've got these advantages, it should be noted what this class can't provide:

  • BWHelper is not suitable when your Actions depend on results of each other.
  • Remember, that if some of your actions take too long, the cancellation may also take a long time.

History

  • 22nd January, 2012 - First published

License

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