Click here to Skip to main content
15,885,875 members
Articles / Programming Languages / C#
Tip/Trick

A Wrapper around BackgroundWorker

Rate me:
Please Sign up or sign in to vote.
3.71/5 (11 votes)
17 Feb 2020CPOL 27.6K   13   16
An alternative way to use BackgroundWorker: more readable, more concise
There are many ways to use BackgroundWorker: lambdas, anonymous delegate and events. All have pros and cons but I find that they are not so readable. Here, you will see an alternative way to use BackgroundWorker.

Introduction

The objective of this tip is to show an alternative way to use BackgroundWorker.

Background

Microsoft documentation shows how to use BackgroundWorker with event and in these examples, it's possible to use other methods.

C#
BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(
    () =>
    {
       // Do Something
    }
);
bgw.DoWork += (sender, e) => { ... }

bgw.DoWork += delegate { ... }

I find these ways not so readable, so I suggest the method in the next chapter.

Using the Code

C#
public class PatientMan
    {
        private Action _DoWork = null;
        private Boolean _IsDoWorkSubscribe = false;
        private DoWorkEventHandler _DoWorkHandler;

        private Action _RunWorkerCompleted = null;
        private Boolean _IsRunWorkerCompletedSubscribe = false;
        private RunWorkerCompletedEventHandler _RunWorkerCompletedHandler;

        private Action<int> _ProgressChanged = null;
        private Boolean _IsProgressChangedSubscribe = false;
        private ProgressChangedEventHandler _ProgressChangedHandler;

        private BackgroundWorker _Worker = new BackgroundWorker();

        public PatientMan()
        {

        }

        public PatientMan(Action doWorkAction,
                            Action runWorkerCompleted)
        {
            this.SubcribeDoWork(doWorkAction);
            this.SubcribeRunWorkerCompleted(runWorkerCompleted);
        }

        private void SubcribeDoWork(Action doWorkAction)
        {
            this._DoWork = doWorkAction;
            this._DoWorkHandler = delegate { this._DoWork(); };
            this._Worker.DoWork += _DoWorkHandler;
            this._IsDoWorkSubscribe = true;
        }

        private void SubcribeRunWorkerCompleted(Action runWorkerCompleted)
        {
            this._RunWorkerCompleted = runWorkerCompleted;
            this._RunWorkerCompletedHandler
                =
            delegate {
                this._RunWorkerCompleted();
                this.UnsubscribeEvents();
            };
            this._Worker.RunWorkerCompleted += this._RunWorkerCompletedHandler;
            this._IsRunWorkerCompletedSubscribe = true;
        }

        private void SubcribeProgressChanged(Action<int> progressChanged)
        {
            this._ProgressChanged = progressChanged;
            this._ProgressChangedHandler = (obj, ev) =>
            {
                this._ProgressChanged(ev.ProgressPercentage);
            };
            this._Worker.ProgressChanged += this._ProgressChangedHandler;
            this._Worker.WorkerReportsProgress = true;
            this._IsProgressChangedSubscribe = true;
        }


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


        public  void ReportProgress(int percentage)
        {
            if(this._ProgressChanged != null)
            {
                if (this._IsProgressChangedSubscribe == true)
                {
                    this._Worker.ReportProgress(percentage);
                }
                else
                {
                    throw new ArgumentNullException();
                }
            }
            else
            {
                throw new NullReferenceException();
            }
        }

        private void UnsubscribeEvents()
        {
            if (this._IsDoWorkSubscribe == true)
            {
                this._Worker.DoWork -= this._DoWorkHandler;
                this._IsDoWorkSubscribe = false;
            }

            if (this._IsRunWorkerCompletedSubscribe == true)
            {
                this._Worker.RunWorkerCompleted -= this._RunWorkerCompletedHandler;
                this._IsRunWorkerCompletedSubscribe = false;
            }

            if (this._IsProgressChangedSubscribe == true)
            {
                this._Worker.ProgressChanged -=  this._ProgressChangedHandler;
                this._Worker.WorkerReportsProgress = false;
                this._IsProgressChangedSubscribe = false;
            }
        }

        public void Dispose()
        {
            this.UnsubscribeEvents();
            this._Worker.Dispose();
        }

        public void SetEvents(Action doWorkAction,
                            Action runWorkerCompleted,
                            Action<int> progressChanged,
                            Boolean unsubscribeEvents = false)
        {
            this.UnsubscribeEvents();

            this.SubcribeDoWork(doWorkAction);
            this.SubcribeRunWorkerCompleted(runWorkerCompleted);
            this.SubcribeProgressChanged(progressChanged);
        }
    }

PatientMan class provides basic features wrapped around BackgroundWorker.

It's possible to use it in this way:

C++
public class SandBoxPatientMan
    {
        public SandBoxPatientMan(int algo)
        {

            switch(algo)
            {
                case 0:
                    {
                        Action doWork = delegate
                        {
                            // Do something long
                            for (int i = 0; i < 10; i++)
                            {
                                System.Threading.Thread.Sleep(1000);
                            }
                        };

                        Action runWorkerCompleted = delegate
                        {
                            // Do something after you did something long

                            Console.WriteLine("runWorkerCompleted " + algo);
                        };

                        PatientMan patientMan = new PatientMan(doWork, runWorkerCompleted);

                        patientMan.RunWorkerAsync();
                    }
                    break;

                case 1:
                    {
                        PatientMan patientMan = new PatientMan();

                        Action doWork = delegate
                        {
                            // Do something long

                            for (int i = 0; i < 10; i++)
                            {
                                System.Threading.Thread.Sleep(1000);

                                patientMan.ReportProgress(i);
                            }
                        };

                        Action runWorkerCompleted = delegate
                        {
                            // Do something after you did something long

                            Console.WriteLine("runWorkerCompleted " + algo);
                        };

                        Action<int> progressChanged = (percentage) =>
                        {
                            // Report a progress during a long process

                            Console.WriteLine("progressChanged " + percentage);
                        };

                        patientMan.SetEvents(doWork, runWorkerCompleted, progressChanged);

                        patientMan.RunWorkerAsync();
                    }
                    break;
            }



        }
    }

Passing delegates via parameter is more readable and concise.

In my experience, it is also easier to debug.

Points of Interest

This class can't replace complete BackgroundWorker, it needs some updates like Cancellation or IsBusy.

I use it often and I hope the reader finds it useful. 

History

In version 2 I updated the code after a suggestion in the comments about memory leaks.

  • 14th February, 2020: Version 1
  • 17th February, 2020: Version 2

License

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


Written By
Software Developer (Senior)
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionVB.net .net core sandbox implementation Pin
Padanian19-Mar-20 4:58
Padanian19-Mar-20 4:58 
QuestionBackgroundWorker Cancellation Pin
george bolovan20-Feb-20 8:49
professionalgeorge bolovan20-Feb-20 8:49 
Questiona question on limits you describe for this code Pin
BillWoodruff18-Feb-20 21:40
professionalBillWoodruff18-Feb-20 21:40 
QuestionIncorrect usage of Dispose(), massive memory leaks Pin
Stacy Dudovitz18-Feb-20 10:42
professionalStacy Dudovitz18-Feb-20 10:42 
I see this often, where its misunderstood where and how to use and implement IDisposable. But lets first explain the problem with this class:

You are implementing a class with a has-a behavior, and which contains an object which exposes an IDisposable interface, namely BackgroundWorker. BackgroundWorker derives from Component, which is defined as follows:
C#
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[DesignerCategory("Component")]
public class Component : MarshalByRefObject, IComponent, IDisposable
{
  private static readonly object EventDisposed = new object();
  private ISite site;
  private EventHandlerList events;

  **<snip>**

Notice that component derives from IDisposable, which means you are now responsible for the lifetime of BackgroundWorker.

When you simply expose Dispose(), you incorrectly attempt to advertise that you can safely handle disposing of your resources. Aside from the fact that you incorrectly implemented the Dispose pattern, I'll prove to you this doesn't work. Try to do the following:
C#
using (var foo = new PatientMan())
{
}

This code will not even compile. This is a very common construct to handle classes that implement IDisposable.

Here is how the PatientMan class should implement IDisposable:
C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ReflectionMadness
{
    class PatientMan : IDisposable
    {
        private BackgroundWorker _Worker = new BackgroundWorker();

        // The rest of the class implementation here

        #region IDisposable Support

        /// <summary>
        /// To detect redundant calls
        /// </summary>
        private bool _isDisposed;
        // ReSharper disable once UnusedMember.Global
        // ReSharper disable once ConvertToAutoPropertyWithPrivateSetter
        public bool IsDisposed => _isDisposed;

        /// <summary>
        /// Finalizer is required to handle cases where Dispose is not explicitly called
        /// Best practices in 2020: We *don't* usually implement finalizers *except* in cases where not releasing resources, or where
        /// we know or suspect resources are unmanaged can be orphaned. In those cases, we should implement a finalizer but understand
        /// the ramifications of not calling Dispose() in application code.
        /// Note: This is still better than orphaning/hanging if the call is never called at all.
        /// </summary>
        ~PatientMan()
        {
            // if we got here, that's bad news.
            Dispose(false);
        }

        /// <summary>
        /// Derived classes can release/cleanup their own resources, but must make sure to check if its already been called
        /// </summary>
        /// <param name="disposing">true if called by application code, false if called from the finalizer</param>
        private void Dispose(bool disposing)
        {
            // Don''t dispose more than once
            if (_isDisposed)
            {
                return;
            }

            // Managed objects should go into this if block. 
            if (disposing)
            {
                if (_Worker != null)
                {
                    _Worker.Dispose();
                    _Worker = null;
                }
            }

            // Indicate that the instance has been disposed.
            _isDisposed = true;
        }

        /// <summary>
        /// Disconnect on disposal
        /// </summary>
        public void Dispose()
        {
            // cleanup the managed objects
            Dispose(true);

            // this call stops the garbage collector from moving this object into generation 2 for sweeping, and marks the object
            // for release. It also prevents the finalizer thread from resurrecting the object and running the finalizer.
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}

Note: In general, we typically DO NOT implement a finalizer of call GC.SupressFinalize, I just wanted to include it here for completeness. The reason has to do with resurrection of dead objects by the finalizer thread and the garbage collector, which is beyond the scope of this comment.
AnswerRe: Incorrect usage of Dispose(), massive memory leaks Pin
Member 1045006018-Feb-20 10:51
Member 1045006018-Feb-20 10:51 
QuestionWhy not use async/await? Pin
gr8gonzo18-Feb-20 10:19
gr8gonzo18-Feb-20 10:19 
AnswerRe: Why not use async/await? Pin
Stacy Dudovitz18-Feb-20 10:52
professionalStacy Dudovitz18-Feb-20 10:52 
QuestionWeird coding standards Pin
ManselD18-Feb-20 7:48
ManselD18-Feb-20 7:48 
QuestionI see a massive flaw here PinPopular
Pete O'Hanlon16-Feb-20 4:30
mvePete O'Hanlon16-Feb-20 4:30 
AnswerRe: I see a massive flaw here Pin
ugo.marchesini17-Feb-20 1:36
professionalugo.marchesini17-Feb-20 1:36 
GeneralRe: I see a massive flaw here Pin
GerVenson18-Feb-20 2:20
professionalGerVenson18-Feb-20 2:20 
GeneralRe: I see a massive flaw here Pin
BillWoodruff18-Feb-20 21:36
professionalBillWoodruff18-Feb-20 21:36 
GeneralRe: I see a massive flaw here Pin
GerVenson23-Feb-20 8:38
professionalGerVenson23-Feb-20 8:38 
GeneralRe: I see a massive flaw here Pin
BillWoodruff25-Feb-20 16:24
professionalBillWoodruff25-Feb-20 16:24 
GeneralRe: I see a massive flaw here Pin
GerVenson25-Feb-20 23:51
professionalGerVenson25-Feb-20 23:51 
GeneralRe: I see a massive flaw here Pin
BillWoodruff29-Feb-20 20:23
professionalBillWoodruff29-Feb-20 20:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.