简体   繁体   English

生成多个线程进行工作,然后等待所有线程完成

[英]Spawn Multiple Threads for work then wait until all finished

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. 例如,我们有一个C#应用程序,该应用程序在启动时会从数据库中各种“类型”表中读取数据,并将该信息存储在一个传递给应用程序的集合中。 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. 目前,应用程序正在同步从10个表中读取数据。 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. 我已经研究了BackGroundWorker,但只希望获得有关完成上述操作的一些建议。

  1. Does the method sound logical in order to speed up the startup time of our application 为了加快应用程序的启动时间,该方法听起来是否合乎逻辑?
  2. 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 我期待一些答案

My preference for this is to handle this via a single WaitHandle, and use Interlocked to avoid locking on a counter: 我的首选是通过一个WaitHandle处理此问题,并使用Interlocked避免锁定计数器:

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. 我喜欢@Reed的解决方案。 Another way to accomplish the same in .NET 4.0 would be to use a CountdownEvent . 在.NET 4.0中完成此操作的另一种方法是使用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. 如Mark所说,如果您有64个以上的STA线程等待句柄。 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 . 如果您不在.NET 4.0上,则可以使用List < ManualResetEvent >,每个线程一个,然后等待它们被设置 To wait on multiple threads you could consider using WaitAll but watch out for the limit of 64 wait handles. 要在多个线程上等待,您可以考虑使用WaitAll,但要注意64个等待句柄的限制。 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. 只需显示GUI,任何丢失的信息都可以通过某种“正在更新...”图标或类似的图标以灰色显示。 When the information comes in, just fire an event to update the GUI. 当信息进入时,只需触发一个事件来更新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: 如果您喜欢冒险,可以使用C#4.0和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. 作为个人喜好,我喜欢使用CountdownEvent类为我做计数。NET4.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. 上面的示例使用ThreadPool ,但是您可以将其交换为您喜欢的任何线程机制。

Another possibility with TPL , assuming jobs is the collections of items to process, or subthreads to run: TPL的另一种可能性是,假设jobs是要处理的项目的集合或要运行的子线程:

Task.WaitAll(jobs
    .Select(job => TaskFactory.StartNew(() => /*run job*/))
    .ToArray());

Just for fun, what @Reed has done, with Monitor. 只是为了好玩,@ Reed使用Monitor完成了什么。 :P :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.");
    }
}

假设数据库读取器线程在完成后立即返回,则可以简单地从启动线程依次对所有十个线程调用Thread.Join。

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/ http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/

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). 如果您使用的是.NET 3.5或更低版本,则可以使用AsyncResult或BackgroundWorker的数组并计算返回的线程数(只是不要忘记通过互锁操作来减少计数器)(请参见http://www.albahari.com / threading /作为参考)。

If you are using .NET 4.0 a parallel for is the simplest approach. 如果使用的是.NET 4.0,则并行是最简单的方法。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM