views:

542

answers:

2

I am relatively new to .NET programming and multithreading in general, and was wondering if it is ok to use .NET provided BackgroundWorker to spawn off worker threads to do some work in a console application? From the various documentation online, I see that intent for this class is more for UI oriented applications, where you want to do some work in background, but keep the UI responsive, and report progress, cancelling processing if needed etc.

In my case, basically I have a controller class where I want to spawn off multiple worker threads to do some processing (limiting the max number of worker threads spawned using a semaphore). Then I want my controller class to block until all the threads have completed processing. So after I start a worker thread to do some work, I want the thread to be able to notify the controller thread when processing is complete. I see that I can use the background worker class, and handle events DoWork and RunWorkerCompleted to accomplish this, however was wondering if this is good idea? Are there better ways to achieve this?

+5  A: 

This won't work in a Console Application, since SynchronizationContext.Current will never be initialized. This is initialized by Windows Forms or WPF for you, when you're using a GUI application.

That being said, there's no reason to do this. Just use ThreadPool.QueueUserWorkItem, and a reset event (ManualResetEvent or AutoResetEvent) to trap the completion state, and block your main thread.


Edit:

After seeing some of the OP comments, I thought I'd add this.

The "nicest" alternative, in my opinion, would be to get a copy of the Rx Framework, since it includes a backport of the TPL in .NET 4. This would allow you to use the overload of Parallel.ForEach which provides the option of supplying a ParallelOptions instance. This will allow you to restrict the total number of concurrent operations, and handle all of the work for you:

// using collection of work items, such as: List<Action<object>> workItems;
var options = new ParallelOptions();
options.MaxDegreeOfParallelism = 10; // Restrict to 10 threads, not recommended!

// Perform all actions in the list, in parallel
Parallel.ForEach(workItems, options, item => { item(null); });

However, using Parallel.ForEach, I'd personally let the system manage the degree of parallelism. It will automatically assign an appropriate number of threads (especially when/if this moves to .NET 4).

Reed Copsey
+6  A: 

If your requirement is just to block until all the threads have finished, that's really easy - just start new threads and then call Thread.Join on each of them:

using System;
using System.Collections.Generic;
using System.Threading;

public class Test
{
    static void Main()
    {
        var threads = new List<Thread>();
        for (int i = 0; i < 10; i++)
        {
            int copy = i;
            Thread thread = new Thread(() => DoWork(copy));
            thread.Start();
            threads.Add(thread);
        }

        Console.WriteLine("Main thread blocking");
        foreach (Thread thread in threads)
        {
            thread.Join();
        }
        Console.WriteLine("Main thread finished");
    }

    static void DoWork(int thread)
    {
        Console.WriteLine("Thread {0} doing work", thread);
        Random rng = new Random(thread); // Seed with unique numbers
        Thread.Sleep(rng.Next(2000));
        Console.WriteLine("Thread {0} done", thread);
    }
}

EDIT: If you have access to .NET 4.0, then the TPL is definitely the right way to go. Otherwise, I would suggest using a producer/consumer queue (there's plenty of sample code around). Basically you have a queue of work items, and as many consumer threads as you have cores (assuming they're CPU-bound; you'd want to tailor it to your work load). Each consumer thread would take items from the queue and process them, one at a time. Exactly how you manage this will depend on your situation, but it's not terribly complicated. It's even easier if you can come up with all the work you need to do to start with, so that threads can just exit when they find the queue is empty.

Jon Skeet
This adds quite a bit of startup time over using ThreadPool threads, however. Starting up your own thread isn't fast, and probably is unnecessary.
Reed Copsey
@Reed: Unless the thread pool is already warmed up, that cost will be there anyway... and it's really *not* that expensive to start up threads. On my **netbook** it takes less than 1 second to start up and join 1000 threads. I don't count that as "quite a bit of startup time" - and it's unlikely that the OP will really need 1000 threads anyway. I regard this as a simpler solution than using the thread-pool.
Jon Skeet
Jon, but with thread.join, I am just blocking the main thread until it finishes. However what if I want to be notified when the specific thread completes, so I can do some further operation in the main thread?
jvtech
@Jon: True, but if you spawn 1000 threads, and all 1000 are doing work, you'll overtax the system, and cause pretty nasty thrashing. There's a reason the ThreadPool limits the number of running threads (the TPL is really good about this in .NET 4, too - since it adjusts the total # of threads at runtime based on work). The OP specifically said he wanted to limit the total number of running threads, which, to me, indicates he's probably spawning a lot of work items...
Reed Copsey
Actually that is exactly why I want the thread to notify the main thread once it operation finishes.. For example if i have say 1000 units of work.. and am using 10 threads to process the work. Thus upon completion of each work item by a thread, i want to spawn off another thread to take next pending work item. That way i want to limit to 10 threads running simultaneously.
jvtech
@jvtech: I just updated my answer with an alternative that makes this much easier, then...
Reed Copsey
@Reed: The ThreadPool actually has very high limits these days - it's not the right way to go about things if all of them are doing a lot of work. There are other (fairly simple) ways to limit the number of threads, even without TPL.
Jon Skeet
Thanks Reed and Jon for your valuable inputs!
jvtech