4
\$\begingroup\$

In my project, I had three BackgroundWorkers that can be run independently, but I also wanted to allow the user to "Run All" of them, but not at the same time. They need to be run one after the other. So, I devised this class to create a Queue of BackgroundWorkers, subscribe to their RunWorkerCompleted event handler, and run the next worker in the Queue.

I would like to know:

  1. If this is even a good idea
  2. If there are any potential bugs I might have overlooked
  3. If I've violated any normal .Net conventions.
  4. Any ways to improve the functionality, speed, or readability of the code

/// <summary>
/// Simplifies running a Queue of BackgroundWorkers one after the other without blocking the current thread. 
/// </summary>
public class BackgroundWorkerQueue
{
    /// <summary>Continue running BackgroundWorkerQueue if any BackgroundWorker causes an Exception.</summary>
    public bool ContinueOnError { get; set; }
    private Queue<QueuedWorker> Queue { get; set; }
    public BackgroundWorkerQueue( )
    {
        this.Queue = new Queue<QueuedWorker>();
    }
    public static BackgroundWorkerQueue operator +( BackgroundWorkerQueue left, BackgroundWorker worker )
    {
        left.Add(worker);
        return left;
    }
    public static BackgroundWorkerQueue operator +( BackgroundWorkerQueue left, QueuedWorker worker )
    {
        left.Add(worker);
        return left;
    }
    /// <summary>Add a BackgroundWorker to the Queue</summary>
    /// <param name="worker">BackgroundWorker to call RunWorkerAsync() on.</param>
    /// <param name="argument">A parameter for use by the background operation to be executed in the System.ComponentModel.BackgroundWorker.DoWork event handler.</param>
    public void Add( BackgroundWorker worker, object argument )
    {
        this.Queue.Enqueue(new QueuedWorker(worker,argument));
    }
    /// <summary>Add a BackgroundWorker to the Queue</summary>
    /// <param name="worker">BackgroundWorker to call RunWorkerAsync() on.</param>
    public void Add( BackgroundWorker worker )
    {
        this.Queue.Enqueue(new QueuedWorker(worker,null));
    }
    /// <summary>Add a BackgroundWorker to the Queue</summary>
    public void Add( QueuedWorker worker )
    {
        this.Queue.Enqueue(worker);
    }
    /// <summary>Starts execution of the BackgroundWorkers.</summary>
    public void Run( )
    {
        Debug.Print("BackgroundWorkerQueue.Run(), {0} items in queue.", this.Queue.Count);
        QueuedWorker q = this.Queue.Dequeue();
        q.Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.Completed);
        q.Worker.RunWorkerAsync(q.Argument);
    }
    private void Completed( object sender, RunWorkerCompletedEventArgs e )
    {
        Debug.Print("BackgroundWorkerQueue.Completed()");
        BackgroundWorker worker = sender as BackgroundWorker;
        if( worker != null )
        {
            worker.RunWorkerCompleted -= this.Completed; // Unsubscribe to event
            if( (this.ContinueOnError || e.Error == null) && this.Queue.Count > 0 )
                this.Run(); // Run the next worker. 
        }
    }
    /// <summary>Object containing a BackgroundWorker and optional Argument to be run.</summary>
    public class QueuedWorker
    {
        /// <summary></summary>A parameter for use by the background operation to be executed in the System.ComponentModel.BackgroundWorker.DoWork event handler.</summary>
        public object Argument { get; set; }
        /// <summary>BackgroundWorker to be run.</summary>
        public BackgroundWorker Worker { get; set; }
        public QueuedWorker()
        {
        }
        /// <param name="worker">BackgroundWorker to call RunWorkerAsync() on.</param>
        /// <param name="argument">A parameter for use by the background operation to be executed in the System.ComponentModel.BackgroundWorker.DoWork event handler.</param>
        public QueuedWorker( BackgroundWorker worker, object argument )
        {
            this.Worker = worker;
            this.Argument = argument;
        }
    }
}

Example Usage:

var q = new BackgroundWorkerQueue();
q.Add(InventoryBgWorker,"FIN STOCK"); // to add a worker, you can call one of the Add(...) methods
q += PartDataBgWorker;                // or you can use the += operator
q += OpenOrdersBgWorker;
q.Run(); // Does not block current thread.
\$\endgroup\$
  • \$\begingroup\$ You could consider creating class that converts/wraps the background workers using TPL (Task / TaskCompletionSource) and then await the workers. Task.WhenAll can also be used to run them simultaneously. \$\endgroup\$ – Nkosi Jan 6 '18 at 2:13
  • 1
    \$\begingroup\$ Or foregoing the background workers altogether and use Tasks blog.stephencleary.com/2013/05/… and blog.stephencleary.com/2013/09/… \$\endgroup\$ – Nkosi Jan 6 '18 at 4:46
4
\$\begingroup\$

You can forego the background workers in favor of Tasks either via Task.Run or just async functions. They can be run independently, all at once or sequentially.

The following example takes a collection of tasks and invokes them one after the other.

//Assuming all the following return Task derived results
var tasks = new Func<Task>[] {
  () => InventoryService.GetInventoryAsync("FIN STOCK"),
  () => PartsService.GetPartDataAsync(),
  () => OrdersService.OpenOrderAsync()
};

Debug.Print("RunAllAsync(), {0} items in collection.", tasks.Length);
foreach (var task in tasks) {
    try {
        await task();
    } catch (Exception e) {
        if (ContinueOnError)
            continue;
        else
            break;
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.