A Queued BackgroundWorker Using Generic Delegates

EDIT: Oops, sorry for the typo in the title. I meant “generic delegates”, not “anonymous delegates”. :)

Last week I wrote a post about using a generic wrapper around the BackgroundWorker class to execute long-running tasks on a different thread. This makes for a more responsive UI since the UI thread is free to process the Windows message pump.

The cool thing about using the BackgroundWorker (and also the BackgroundWorkerHelper) is that no matter how many times the user presses the button to start a long-running process, a new worker thread is created, executed, and marshalled back to the UI thread. This means that if you press a button that, say, runs a 5-second process at t=0, 1, 2, and 3 seconds, then you’ll see a result at t=5, 6, 7, and 8 seconds.

Most of the time this is the desired response. Recently I just ran into a particular scenario where I didn’t want the second long-running process to start until the first finished. In my case, I had a button that the user could press to print the current page. If they pressed the button again before the page had finished printing, then an error would be thrown. What I really need is a BackgroundWorker that queues up each new call to it and executes the next call only when the previous call has completed.

We can combine the same anonymous wrapper approach with a Queue to achieve this exact functionality! Here’s the code for the QueuedBackgroundWorker:

public static class QueuedBackgroundWorker
    {
        public static void QueueWorkItem(
            Queue> queue,
            Tin inputArgument,
            Func, Tout> 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((Tin)args.Argument));
                }
            };
            bw.RunWorkerCompleted += (sender, args) =>
            {
                if (workerCompleted != null)
                {
                    workerCompleted(new WorkerResult((Tout)args.Result, args.Error));
                }
                queue.Dequeue();
                if (queue.Count > 0)
                {
                    QueueItem nextItem = queue.Peek();
                    nextItem.BackgroundWorker.RunWorkerAsync(nextItem.Argument);
                }
            };

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

Along with these generic helper classes:

    public class DoWorkArgument
    {
        public DoWorkArgument(T argument)
        {
            this.Argument = argument;
        }
        public T Argument { get; private set; }
    }

    public class WorkerResult
    {
        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
    {
        public QueueItem(BackgroundWorker backgroundWorker, Tin argument)
        {
            this.BackgroundWorker = backgroundWorker;
            this.Argument = argument;
        }

        public Tin Argument { get; private set; }
        public BackgroundWorker BackgroundWorker { get; private set; }
    }

To test this, I created a quick-n-dirty WinForms application with a button and a textbox. When the user preses the button, a new item is added to the QueuedBackgroundWorker’s Queue. When the long-running (in this case 1 second) process completes, it appends a message to the textbox. For example:

        private Queue> m_Queue = new Queue>();
        private int m_Count = 0;

        private void btnStart_Click(object sender, EventArgs e)
        {
            QueuedBackgroundWorker.QueueWorkItem(
                m_Queue,
                m_Count++,
                args =>
                {
                    string now = DateTime.Now.ToLongTimeString();
                    int count = args.Argument;
                    Thread.Sleep(1000);
                    string message = String.Format("Inside background thread: Time={0} Count=[{1}]", now, count);
                    return new { Count = count, Message = message };
                },
                args =>
                {
                    string now = DateTime.Now.ToLongTimeString();
                    int count = args.Result.Count;
                    string message = args.Result.Message;
                    string line = String.Format("Received Result: Time={0} Count={1} Message=[{2}]{3}",
                        now, count, message, Environment.NewLine);
                    txtOutput.AppendText(line);
                });
        }

After testing it out, it behaves exactly as expected :) I can now use this in my application to queue up things to print. Each item will only begin printing after the previous one completes. Here’s a screenshot:

Queued BackgroundWorker output

Essentially what we’ve done here is create a collection (queue) of input objects for the DoWork method of the BackgroundWorker. The really cool part is that the generic delegate for this method is type-safe and can be used with anonymous types (as seen in the above example, just for fun).

I’m really digging all of these new .NET 3.0 features :)

Hope someone finds that useful!


You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

AddThis Social Bookmark Button

18 Responses to “A Queued BackgroundWorker Using Generic Delegates”

  1. [...] A Queued BackgroundWorker Using Generic Delegates [...]

  2. Flavio Zanellato Says:

    Very good example ! But I’m not yet very familiar with C#
    syntax, specially with lambda expression.
    Do you have a possibility to convert the code in VB.NET ?
    Thank you in advance.

  3. Thanks Flavio. Unfortunately you’re out of luck — lambda expressions are not supported in VB.NET. You can always just use a named method, but it’s not as clean or readable as C#.

  4. Flavio Zanellato Says:

    Lambda expressions not supported ?
    I have written code in VS2008 (VB.NET) with lambda expressions…

  5. Ah, well if you’ve used them in VB.NET then my complete ignorance of VB is showing :) Best of luck with that. I thought I remembered reading something awhile back about no (or only partial) lambda support in VB but I could be wrong.

  6. Yes, VB.NET does support Lambdas, but they are a crippled version that you cannot use with the BackgroundWorker component.

    In VB.NET Lambdas you cannot use expressions that do not return a value (they must all be functions) and you cannot create multi-line lambdas.

  7. first of all, thank you for posting this great background worker code. i’ll freely admit that i’m new to delegates and lambdas, and i’m not quite sure how to add a background worker progress report method to what you already have here. is it possible to add a progress reporter to this? if so, would you provide some pointers on how i could go about it? thanks again!

  8. Hi,
    There are no left brackets in the code…see sample below. Is it possible to download the file somewhere?

    public static void QueueWorkItem(
    Queue> queue,
    Tin inputArgument,
    Func, Tout> doWork,
    Action> workerCompleted)

  9. HTML is eating some of the signs. For anyone looking at this page and wanting to grab the code: If you View..Source (or whatever works to see the HTML code off your browser), you can get the complete code segments off the website.

  10. Awesome piece of code! I haven’t played around with even the new .NET 3.0 features yet but it looks like there’s some great new additions.

    Cheers!

  11. Hi Matt. a colleague of mine and I were discussing the code sample you posted, and we were both wondering about your reasoning behind the design of this solution. Why are you queing up individual background threads, instead of sending the data you want processed synchronously to one background thread? Wouldn’t queing up additional threads create a lot of unnecesary overhead?

    Thanks!
    -John

  12. Matt, thanks for providing this example.

    This solution is exactly what I needed for the application I’m writing. Your code required reading MSDN on my part, because I didn’t understand lambda and parameter typing, but it forced me to learn.

    Thanks.

  13. Balan Sinniah Says:

    Hi Matt,
    THanks for the posting. I find it very useful in my project. However, I am wondering if turning the SupportsCancellation flag on would stop the background worker. I noticed that the cancelAsync is not implemented in this sample. THe scenario is that I need to cancel the background worker when I close my winform ( while the background worker still performing a task). Please advice.

    Thanks

    Regards,
    Balan Sinniah

  14. any real samples to call Querys (executenonquery) Oracle ?? thanks

  15. hi,you first code have some question
    public static void QueueWorkItem(
    Queue> queue,
    Tin inputArgument,
    Func, Tout> doWork,
    Action> workerCompleted)

    i think it have some indent problem,please paste it again ,thank you.

  16. [...] What I found was an unbelievably perfect solution from Matt Valerio with his post titled "A Queued BackgroundWorker Using Generic Delegates".  As the title suggests, he wrote a class called “QueuedBackgroundWorker” that would add [...]

  17. Hi,

    Good article, I tried to reproduce this with ReportProgress too, but getting cross-thread exception (InvalidOperationException), can you update with a ReportProgress option too please?

    Thanks,
    Nanda

  18. Thanks for this article. I have a question. I would like to pass a function that takes in parameter, but in the argument line it doesn’t allow you to pass parameter as it is a Action of T Implementation. I need my argument to be readily available so when the worker get to my item function it knows the object. How would i do that?

Leave a Reply