Creating BackgroundWorker with Queue

14,293

Solution 1

Here's a much shorter method that does what you want:

public class BackgroundQueue
{
    private Task previousTask = Task.FromResult(true);
    private object key = new object();
    public Task QueueTask(Action action)
    {
        lock (key)
        {
            previousTask = previousTask.ContinueWith(t => action()
                , CancellationToken.None
                , TaskContinuationOptions.None
                , TaskScheduler.Default);
            return previousTask;
        }
    }

    public Task<T> QueueTask<T>(Func<T> work)
    {
        lock (key)
        {
            var task = previousTask.ContinueWith(t => work()
                , CancellationToken.None
                , TaskContinuationOptions.None
                , TaskScheduler.Default);
            previousTask = task;
            return task;
        }
    }
}

By adding each new action as a continuation of the previous you ensure that only one is worked on at a time, as the next item won't start until the previous item is finished, you ensure that there is no thread sitting around idling when there is nothing to be worked on, and you ensure they're all done in order.

Also note that if you only ever think you'll need one queue, and not any number, you could make all of the members static, but that's up to you.

Solution 2

It seems you are missing the second generic parameter - Tout;

The following code should take care of it:

using System;
using System.Collections.Generic;
using System.ComponentModel;

public static class QueuedBackgroundWorker
{
    public static void QueueWorkItem<Tin, Tout>(
        Queue<QueueItem<Tin>> queue,
        Tin inputArgument,
        Func<DoWorkArgument<Tin>, Tout> doWork,
        Action<WorkerResult<Tout>> workerCompleted)
    {
        if (queue == null) throw new ArgumentNullException("queue");

        BackgroundWorker bw = new BackgroundWorker();
        bw.WorkerReportsProgress = false;
        bw.WorkerSupportsCancellation = false;
        bw.DoWork += (sender, args) =>
            {
                if (doWork != null)
                {
                    args.Result = doWork(new DoWorkArgument<Tin>((Tin)args.Argument));
                }
            };
        bw.RunWorkerCompleted += (sender, args) =>
            {
                if (workerCompleted != null)
                {
                    workerCompleted(new WorkerResult<Tout>((Tout)args.Result, args.Error));
                }
                queue.Dequeue();
                if (queue.Count > 0)
                {
                    QueueItem<Tin> nextItem = queue.Peek(); // as QueueItem<T>;
                    nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
                }
            };

        queue.Enqueue(new QueueItem<Tin>(bw, inputArgument));
        if (queue.Count == 1)
        {
            QueueItem<Tin> nextItem = queue.Peek() as QueueItem<Tin>;
            nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
        }
    }
}

public class DoWorkArgument<T>
{
    public DoWorkArgument(T argument)
    {
        this.Argument = argument;
    }

    public T Argument { get; private set; }
}

public class WorkerResult<T>
{
    public WorkerResult(T result, Exception error)
    {
        this.Result = result;
        this.Error = error;
    }

    public T Result { get; private set; }

    public Exception Error { get; private set; }
}

public class QueueItem<T>
{
    public QueueItem(BackgroundWorker backgroundWorker, T argument)
    {
        this.BackgroundWorker = backgroundWorker;
        this.Argument = argument;
    }

    public T Argument { get; private set; }

    public BackgroundWorker BackgroundWorker { get; private set; }
}

And the usage should be:

    private readonly Queue<QueueItem<int>> _workerQueue = new Queue<QueueItem<int>>();
    private int _workerId = 1;

    [Test]
    public void BackgroundTest()
    {
        QueuedBackgroundWorker.QueueWorkItem(
            this._workerQueue, 
            this._workerId++,
            args =>  // DoWork
                {
                    var currentTaskId = args.Argument;
                    var now = DateTime.Now.ToLongTimeString();
                    var message = string.Format("DoWork thread started at '{0}': Task Number={1}", now, currentTaskId);
                    return new { WorkerId = currentTaskId, Message = message };
                },
            args =>  // RunWorkerCompleted
                {
                    var currentWorkerId = args.Result.WorkerId;
                    var msg = args.Result.Message;

                    var now  = DateTime.Now.ToShortTimeString();
                    var completeMessage = string.Format(
                        "RunWorkerCompleted completed at '{0}'; for Task Number={1}, DoWork Message={2}",
                        now,
                        currentWorkerId,
                        msg);
                }
            );
    }
Share:
14,293
Libor Zapletal
Author by

Libor Zapletal

Updated on June 04, 2022

Comments

  • Libor Zapletal
    Libor Zapletal almost 2 years

    I need to create queue and use it with BackgroundWorker. So I can add operations and when one is done next is starting in background. I found this code by google:

    public class QueuedBackgroundWorker<T>
    {
        public void QueueWorkItem(
            Queue queue,
            T inputArgument,
            Func<T> doWork,
            Action workerCompleted)
        {
            if (queue == null) throw new ArgumentNullException("queue");
    
            BackgroundWorker bw = new BackgroundWorker();
            bw.WorkerReportsProgress = false;
            bw.WorkerSupportsCancellation = false;
            bw.DoWork += (sender, args) =>
            {
                if (doWork != null)
                {
                    args.Result = doWork(new DoWorkArgument<T>((T)args.Argument));
                }
            };
            bw.RunWorkerCompleted += (sender, args) =>
            {
                if (workerCompleted != null)
                {
                    workerCompleted(new WorkerResult<T>((T)args.Result, args.Error));
                }
                queue.Dequeue();
                if (queue.Count > 0)
                {
                    QueueItem<T> nextItem = queue.Peek() as QueueItem<T>;
                    nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
                }
            };
    
            queue.Enqueue(new QueueItem<T>(bw, inputArgument));
            if (queue.Count == 1)
            {
                QueueItem<T> nextItem = queue.Peek() as QueueItem<T>;
                nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
            }
        }
    }
    
    public class DoWorkArgument<T>
    {
        public DoWorkArgument(T argument)
        {
            this.Argument = argument;
        }
        public T Argument { get; private set; }
    }
    
    public class WorkerResult<T>
    {
        public WorkerResult(T result, Exception error)
        {
            this.Result = result;
            this.Error = error;
        }
    
        public T Result { get; private set; }
        public Exception Error { get; private set; }
    }
    
    public class QueueItem<T>
    {
        public QueueItem(BackgroundWorker backgroundWorker, T argument)
        {
            this.BackgroundWorker = backgroundWorker;
            this.Argument = argument;
        }
    
        public T Argument { get; private set; }
        public BackgroundWorker BackgroundWorker { get; private set; }
    }
    

    But I have problem with doWork and workerCompleted. I get error:

    Delegate 'Func' does not take 1 arguments

    How can I fix this? How should I change parameters? Thanks

  • Libor Zapletal
    Libor Zapletal about 11 years
    Looks better, thanks for this. Now I am using it like this: bgQueue.QueueTask(() => CreateConvertingProccess(item.FullName));. Can you help me how can I call method when one task is done? Should I added it after lock? I mean when I want to add method which I would add to RunWorkerCompleted.
  • Libor Zapletal
    Libor Zapletal about 11 years
    Thanks very much, It's working. I must learn about working with Task.
  • Libor Zapletal
    Libor Zapletal about 11 years
    Can you help me once more please. How can I find out if queue is empty?
  • Servy
    Servy about 11 years
    public bool IsEmpty() { return previousTask.IsCompleted; }
  • Admin
    Admin about 8 years
    You have made what I thought to be impossible a dream come true. Thank you for this.
  • Geovani Martinez
    Geovani Martinez over 6 years
    @servy This is interesting... Could you share an example of how it can be put to for something like processing a list of files (given file paths)?
  • Servy
    Servy over 6 years
    @GeovaniMartinez It doesn't matter what the tasks are doing. That's the whole point. You construct a queue and provide methods and they'll all be run sequentially, asynchronously. What they're doing is irrelevant to the queue.
  • stigzler
    stigzler about 2 years
    It would have been helpful to give an example of the code to add to the queue. I'm currently trying to convert this to vb and struggling + have no way to know if it's the way I'm using the QueueTask method: bgwQueue.QueueTask(New Action(Sub() Wassiknacker() End Sub))