Using .NET BackgroundWorker class in console app
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 开发者_StackOverflow社区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?
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.
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).
You are right that BackgroundWorker will not work here. It is indeed designed to Work with GUI environments (WinForms and WPF alike) where the main Thread is permanently busy checking/executing the Message Pump (wich processes all the Windows Events like Click and Resize, plus the ones comming from backgroundWorker). Without the Event Queue the Mainthread will run out. And without the Event queue, BackgroundWorker cannot invoke the Callbacks on the Main Thread either.
Indeed doing Multithreading on a Console is a dozen times harder because you don't have the Message Pump to solve the two big issues: how to keep Main Thread alive and how to inform the main Thread of Changes needed on the UI. Event based Programming prooved to be the ideal learning ground for Multithreading.
There are some solutions:
Expanding your Console App with a Message Pump. Since it is little more then a Thread Shared Collection of Delegates (where other threads only add Stuff) and a while loop it's surprisingly easy: C# Console App + Event Handling
The ThreadPool and Thread Classes. They have been around as long as the BackgroundWorker (since .NET 2.0) and seem to be designed to solve the issue for Console. You still need to block your Main Thread with a loop. ThreadPool will also limit the Number of Threads to something suiteable on the Real machine. You should avoid fixed Thread limits and instead rely on the ThreadPooling features of the OS to handle the question of "how many Thread should run at once?". For example, fixed Number of 5 Threads might be ideal for your 6 Core Development Machine. But it would be too many for a Single Core Desktop Computer. And would be an pointless Bottleneck on a Server wich has easily between 16 and 64 Cores with GiB's of RAM to spare.
If you have .NET 4.0 or later, the newly added Task Paralellism might be worth a look. http://msdn.microsoft.com/en-us/library/dd537609.aspx It has built in ThreadPooling, limited Priorization abilities and a can chain and join multiple Tasks easily (everything Thread could do, Task can do). Using Task also enables the use of async and await Keywords: http://msdn.microsoft.com/en-us/library/hh191443.aspx Again, you do not get around blocking the Main Thread in a Console App.
Well, sorry to necro-answer, but here in 2022 and this answer comes up tops for "console application worker thread" at the moment. Now, I just started using C#/.Net like last week (coming from much C[++]), so clearly I may be missing something. And have not actually used it in an app yet. But... it does seem to work.
With .NET 5.0 (no clue about other frameworks)
(Example has some paranoid and impatient tendencies.)
using System.ComponentModel; // for BackgroundWorker
using Console = System.Console;
class Program
{
static void Main(string[] args) {
BackgroundWorker worker = new();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.ProgressChanged += worker_ProgressChanged;
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
Console.WriteLine("Starting worker... (any key to cancel/exit)");
worker.RunWorkerAsync();
Console.ReadKey(true); // event loop
if (worker.IsBusy) {
Console.WriteLine("Interrupting the worker...");
worker.CancelAsync();
var sw = System.Diagnostics.Stopwatch.StartNew();
while (worker.IsBusy && sw.ElapsedMilliseconds < 5000)
System.Threading.Thread.Sleep(1);
}
}
static void worker_ProgressChanged(object _, ProgressChangedEventArgs e) {
Console.WriteLine("Worker progress: {0:d}%", e.ProgressPercentage);
}
static void worker_DoWork(object sender, DoWorkEventArgs e) {
Console.WriteLine("Worker: Starting to do some work now...");
BackgroundWorker worker = sender as BackgroundWorker;
e.Result = 0;
for (int i = 1; i < 11; ++i) {
for (int ii=0; !worker.CancellationPending && ii < 10; ++ii)
System.Threading.Thread.Sleep(100);
if (worker.CancellationPending)
break;
worker.ReportProgress((int)((100.0 * i) / 10));
e.Result = i;
}
e.Cancel = worker.CancellationPending;
}
static void worker_RunWorkerCompleted(object _, RunWorkerCompletedEventArgs e) {
if (e.Cancelled) {
Console.WriteLine("Worker: I wuz busy!");
return;
}
Console.WriteLine("Worker: I worked {0:D} times.", e.Result);
Console.WriteLine("Worker: Done now!");
}
}
Originally found at https://www.codeproject.com/Questions/625473/backgroundWorker-in-csharp (posted on July 2013 so I assume this has worked for a while?).
精彩评论