Click here to Skip to main content
Licence CPOL
First Posted 23 Jan 2012
Views 4,029
Downloads 780
Bookmarked 29 times

BackgroundWorker helper

By | 23 Jan 2012 | Article
A class, that may shorten your time, spent with BackgroundWorker, and handles some logics of working with it.

Introduction

BackgroundWorker class is used to manage the work that is done in a background thread and report progress to the UI avoiding cross-threading problems. Implementing all BackgroundWorker logics is a bit dirty and does takes 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

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).

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 signalled by the means of ValueMonitor.ValueChanged event rather then doing this using BackgroundWorker, you should implement the cross-threading logics yourself. In the example project for the sake of simplicity and because I`m lazy, the CheckForIllegelCrossThreadCalls property is set to true.

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 buttin is hit, the following code is executed:

            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 be worried about ReportProgress method.
  • Average time left is measured. You may handle BWHelper.TimeLeft.ValueChanged event to show this at UI.
  • Easy switch between sequential and parallel actions execution.
  • Cancellation logics 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 take also a long time.

History

Jan, 22 2012 - First published

License

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

About the Author

kosmoh

Software Developer
Crypton-M
Ukraine Ukraine

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120517.1 | Last Updated 23 Jan 2012
Article Copyright 2012 by kosmoh
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid