问题
just want some advice on "best practice" regarding multi-threading tasks.
as an example, we have a C# application that upon startup reads data from various "type" table in our database and stores the information in a collection which we pass around the application. this prevents us from hitting the database each time this information is required.
at the moment the application is reading data from 10 tables synchronously. i would really like to have the application read from each table in a different thread all running in parallel. the application would wait for all the threads to complete before continuing with the startup of the application.
i have looked into BackGroundWorker but just want some advice on accomplishing the above.
- Does the method sound logical in order to speed up the startup time of our application
- How can we best handle all the threads keeping in mind that each thread's work is independent of one another, we just need to wait for all the threads to complete before continuing.
i look forward to some answers
回答1:
My preference for this is to handle this via a single WaitHandle, and use Interlocked to avoid locking on a counter:
class Program
{
static void Main(string[] args)
{
int numThreads = 10;
ManualResetEvent resetEvent = new ManualResetEvent(false);
int toProcess = numThreads;
// Start workers.
for (int i = 0; i < numThreads; i++)
{
new Thread(delegate()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
// If we're the last thread, signal
if (Interlocked.Decrement(ref toProcess) == 0)
resetEvent.Set();
}).Start();
}
// Wait for workers.
resetEvent.WaitOne();
Console.WriteLine("Finished.");
}
}
This works well, and scales to any number of threads processing, without introducing locking.
回答2:
I like @Reed's solution. Another way to accomplish the same in .NET 4.0 would be to use a CountdownEvent.
class Program
{
static void Main(string[] args)
{
var numThreads = 10;
var countdownEvent = new CountdownEvent(numThreads);
// Start workers.
for (var i = 0; i < numThreads; i++)
{
new Thread(delegate()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
// Signal the CountdownEvent.
countdownEvent.Signal();
}).Start();
}
// Wait for workers.
countdownEvent.Wait();
Console.WriteLine("Finished.");
}
}
回答3:
If you have more than 64 wait handles for an STA Thread as Mark says. you could create a list with your threads and wait for all to complete in a second loop.
//check that all threads have completed.
foreach (Thread thread in threadList)
{
thread.Join();
}
回答4:
If you're not on .NET 4.0 then you can use a List<ManualResetEvent>, one for each thread and Wait for them to be Set. To wait on multiple threads you could consider using WaitAll but watch out for the limit of 64 wait handles. If you need more than this, you can just loop over them and wait for each one individually.
If you want a faster startup exprience, you probably don't need to wait for all the data to be read during startup. Just display the GUI and any information that is missing can be shown greyed out with some sort of "Updating..." icon or similar. When the information comes in, just fire an event to update the GUI. There could be many operations that the user can begin to perform even before all the data from all tables is read in.
回答5:
If you're feeling adventurous you can use C# 4.0 and the Task Parallel Library:
Parallel.ForEach(jobList, curJob => {
curJob.Process()
});
回答6:
Here are two patterns for waiting on multiple parallel operations. The trick is that you have to treat your main thread as if it were one of the parallel operations as well. Otherwise, there is a subtle race condition between the signalling of completion in the worker threads and the waiting on that signal from the main thread.
int threadCount = 1;
ManualResetEvent finished = new ManualResetEvent(false);
for (int i = 0; i < NUM_WORK_ITEMS; i++)
{
Interlocked.Increment(ref threadCount);
ThreadPool.QueueUserWorkItem(delegate
{
try
{
// do work
}
finally
{
if (Interlocked.Decrement(ref threadCount) == 0) finished.Set();
}
});
}
if (Interlocked.Decrement(ref threadCount) == 0) finished.Set();
finished.WaitOne();
As a personal preference I like using the CountdownEvent class to do the counting for me which is available in .NET 4.0.
var finished = new CountdownEvent(1);
for (int i = 0; i < NUM_WORK_ITEMS; i++)
{
finished.AddCount();
ThreadPool.QueueUserWorkItem(delegate
{
try
{
// do work
}
finally
{
finished.Signal();
}
});
}
finished.Signal();
finished.Wait();
The examples above use the ThreadPool
, but you can swap that for whatever threading mechanism you prefer.
回答7:
Just for fun, what @Reed has done, with Monitor. :P
class Program
{
static void Main(string[] args)
{
int numThreads = 10;
int toProcess = numThreads;
object syncRoot = new object();
// Start workers.
for (int i = 0; i < numThreads; i++)
{
new Thread(delegate()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
// If we're the last thread, signal
if (Interlocked.Decrement(ref toProcess) == 0)
{
lock (syncRoot)
{
Monitor.Pulse(syncRoot);
}
}
}).Start();
}
// Wait for workers.
lock (syncRoot)
{
if (toProcess > 0)
{
Monitor.Wait(syncRoot);
}
}
Console.WriteLine("Finished.");
}
}
回答8:
Another possibility with TPL, assuming jobs
is the collections of items to process, or subthreads to run:
Task.WaitAll(jobs
.Select(job => TaskFactory.StartNew(() => /*run job*/))
.ToArray());
回答9:
Assuming the database reader threads return as soon as they're done, you can simply call Thread.Join on all ten threads in turn from the initiating thread.
回答10:
If you are using .NET 3.5 or below, you can use an array of AsyncResult or BackgroundWorker and count how many threads have returned (just don't forget to decrease counter with interlocked operations) (see http://www.albahari.com/threading/ as a reference).
If you are using .NET 4.0 a parallel for is the simplest approach.
回答11:
an easier method I like to use:
private int ThreadsCount = 100; //initialize threads count
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < ThreadsCount; i++)
{
Thread t = new Thread(new ThreadStart(myMethod));
t.IsBackground = true;
t.Start();
}
}
private void myMethod()
{
//everytime a thread finishes executing decrease the threads count
ThreadsCount = ThreadsCount - 1;
if (ThreadsCount < 1)
{
//if all threads finished executing do whatever you wanna do here..
MessageBox.Show("Finished Executing all threads!!!");
}
}
回答12:
Posting to maybe help some others, spent quite a bit of time looking for a solution like what I came up with. So I took a little different approach. I was spinning off numerous threads and incremented a counter and decremented a counter as a thread started and stopped. Then in the main method I was wanting to pause and wait for threads to complete I did.
while (threadCounter > 0)
{
Thread.Sleep(500); //Make it pause for half second so that we don’t spin the cpu out of control.
}
Documented on my blog. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/
来源:https://stackoverflow.com/questions/2528907/spawn-multiple-threads-for-work-then-wait-until-all-finished