Spawn Multiple Threads for work then wait until all finished

允我心安 提交于 2019-11-26 21:15:51

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.

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.");
    }
}

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();

}  

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.

If you're feeling adventurous you can use C# 4.0 and the Task Parallel Library:

Parallel.ForEach(jobList, curJob => {
  curJob.Process()
});

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.

Andras Vass

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.");
    }
}

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());

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.

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.

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!!!");
        }
    }

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/

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!