【问题标题】:Create multiple threads and wait for all of them to complete创建多个线程并等待所有线程完成
【发布时间】:2011-05-10 14:37:28
【问题描述】:

如何创建多个线程并等待所有线程完成?

【问题讨论】:

    标签: c# .net multithreading


    【解决方案1】:

    这取决于您使用的 .NET Framework 版本。 .NET 4.0 使用 Tasks 使线程管理变得更加容易:

    class Program
    {
        static void Main(string[] args)
        {
            Task task1 = Task.Factory.StartNew(() => doStuff());
            Task task2 = Task.Factory.StartNew(() => doStuff());
            Task task3 = Task.Factory.StartNew(() => doStuff());
    
            Task.WaitAll(task1, task2, task3);
                    Console.WriteLine("All threads complete");
        }
    
        static void doStuff()
        {
            //do stuff here
        }
    }
    

    在以前的 .NET 版本中,您可以使用 BackgroundWorker 对象、使用 ThreadPool.QueueUserWorkItem(),或者手动创建线程并使用 Thread.Join() 等待它们完成:

    static void Main(string[] args)
    {
        Thread t1 = new Thread(doStuff);
        t1.Start();
    
        Thread t2 = new Thread(doStuff);
        t2.Start();
    
        Thread t3 = new Thread(doStuff);
        t3.Start();
    
        t1.Join();
        t2.Join();
        t3.Join();
    
        Console.WriteLine("All threads complete");
    }
    

    【讨论】:

    • Task API 是迄今为止最干净的解决方案。
    • 有一些限制需要注意——如果你需要一个线程有一个特定的优先级,那么你就不能使用任务。好吧,从技术上讲你可以,但是在 Task 中更改线程的线程优先级是个坏主意,因为该线程属于线程池,因此您的自定义优先级可能会影响其他一些代码。
    【解决方案2】:

    我认为你需要WaitHandler.WaitAll。这是一个例子:

    public static void Main(string[] args)
    {
        int numOfThreads = 10;
        WaitHandle[] waitHandles = new WaitHandle[numOfThreads];
    
        for (int i = 0; i < numOfThreads; i++)
        {
            var j = i;
            // Or you can use AutoResetEvent/ManualResetEvent
            var handle = new EventWaitHandle(false, EventResetMode.ManualReset);
            var thread = new Thread(() =>
                                    {
                                        Thread.Sleep(j * 1000);
                                        Console.WriteLine("Thread{0} exits", j);
                                        handle.Set();
                                    });
            waitHandles[j] = handle;
            thread.Start();
        }
        WaitHandle.WaitAll(waitHandles);
        Console.WriteLine("Main thread exits");
        Console.Read();
    }
    

    FCL 有一些更方便的功能。

    (1) Task.WaitAll,以及它的重载,当您想要并行执行一些任务(并且没有返回值)时。

    var tasks = new[]
    {
        Task.Factory.StartNew(() => DoSomething1()),
        Task.Factory.StartNew(() => DoSomething2()),
        Task.Factory.StartNew(() => DoSomething3())
    };
    Task.WaitAll(tasks);
    

    (2) Task.WhenAll 当你想用返回值做一些任务时。它执行操作并将结果放入数组中。它是线程安全的,您不需要使用线程安全的容器并自己实现添加操作。

    var tasks = new[]
    {
        Task.Factory.StartNew(() => GetSomething1()),
        Task.Factory.StartNew(() => GetSomething2()),
        Task.Factory.StartNew(() => GetSomething3())
    };
    var things = Task.WhenAll(tasks);
    

    【讨论】:

    • @Kirk:我刚才准备添加一个例子,但不得不去开会。
    • 很好地解释了方法中的“不返回”和“返回”值!
    • 您的线程代码正是我想要的,因为我想模拟大量请求,而不仅仅是使用“更智能”的 TPL 方法给我的少数线程。
    【解决方案3】:

    我做了一个非常简单的扩展方法来等待一个集合的所有线程:

    using System.Collections.Generic;
    using System.Threading;
    
    namespace Extensions {
        public static class ThreadExtension {
            public static void WaitAll (this IEnumerable<Thread> threads) {
                if (threads != null) {
                    foreach (Thread thread in threads) {
                        thread.Join();
                    }
                }
            }
        }
    }
    

    然后你只需调用:

    List<Thread> threads = new List<Thread>();
    // Add your threads to this collection
    threads.WaitAll();
    

    【讨论】:

    • 我宁愿使用ThreadHelpers.WaitAll(threadCollection) .. 无论如何,这主要是我用于测试的。在实际代码中,我很少需要“等待所有”。
    • 解释一下。例如,工作原理是什么?
    【解决方案4】:

    在 .NET 4.0 中,您可以使用 Task Parallel Library

    在早期版本中,您可以在循环中创建Thread 对象列表,对每个对象调用Start,然后再创建一个循环并在每个对象上调用Join

    【讨论】:

    • @user:如果您在启动线程后立即调用Join,您最终将等待它完成,然后再启动任何其他线程。您需要启动所有线程,然后Join所有线程。
    【解决方案5】:

    如果您不想使用 Task class(例如,在 .NET 3.5 中),您可以启动所有线程,然后将它们添加到列表中,然后将它们添加到 join foreach 中循环。

    例子:

    List<Thread> threads = new List<Thread>();
    
    // Start threads
    for (int i = 0; i < 10; i++) {
        int tmp = i; // Copy value for closure
        Thread t = new Thread(() => Console.WriteLine(tmp));
        t.Start();
        threads.Add(t);
    }
    
    // Join threads (wait threads)
    foreach (Thread thread in threads) {
        thread.Join();
    }
    

    【讨论】:

      【解决方案6】:

      大多数建议的答案都没有考虑超时间隔,这对于防止可能的死锁非常重要。接下来是我的示例代码。 (请注意,我主要是 Win32 开发人员,这就是我在那里做的方式。)

      //'arrRunningThreads' = List<Thread>
      
      //Wait for all threads
      const int knmsMaxWait = 3 * 1000;           //3 sec timeout
      int nmsBeginTicks = Environment.TickCount;
      foreach(Thread thrd in arrRunningThreads)
      {
          //See time left
          int nmsElapsed = Environment.TickCount - nmsBeginTicks;
          int nmsRemain = knmsMaxWait - nmsElapsed;
          if(nmsRemain < 0)
              nmsRemain = 0;
      
          //Then wait for thread to exit
          if(!thrd.Join(nmsRemain))
          {
              //It didn't exit in time, terminate it
              thrd.Abort();
      
              //Issue a debugger warning
              Debug.Assert(false, "Terminated thread");
          }
      }
      

      【讨论】:

        【解决方案7】:

        我不知道是否有更好的方法,但下面描述了我是如何使用计数器和后台工作人员线程做到的。

        private object _lock = new object();
        private int _runningThreads = 0;
        
        private int Counter{
            get{
                lock(_lock)
                    return _runningThreads;
            }
            set{
                lock(_lock)
                    _runningThreads = value;
            }
        }
        

        现在,每当您创建工作线程时,都会增加计数器:

        var t = new BackgroundWorker();
        // Add RunWorkerCompleted handler
        
        // Start thread
        Counter++;
        

        在工作完成后,递减计数器:

        private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Counter--;
        }
        

        现在您可以随时检查计数器以查看是否有线程在运行:

        if(Couonter>0){
            // Some thread is yet to finish.
        }
        

        【讨论】:

        • 你不需要锁。实际上,由于您只从 UI 线程中编写属性,因此您不需要任何东西。
        • 我可能弄错了,但我认为锁是正确的;他正在减少每个工作人员完成时触发的事件处理程序中的计数器。
        • @Mark: Completed 事件总是在 UI 线程上触发。此外,锁不会有任何作用; Int32 读写是原子的。如果存在线程问题,锁将无济于事;他需要打电话给Interlocked.Increment
        • 锁对 Counter 不起作用——因为在 get/set 之间计数器是不受保护的。
        • @Greg:没错。多线程很难。在每个成员中敲一个锁是远远不够的。
        【解决方案8】:

        就我而言,我无法使用Task.Run()Task.Factory.StartNew() 在线程池中实例化我的对象。他们不会正确同步我长时间运行的委托。

        我需要委托异步运行,暂停我的主线程以使其集体完成。 Thread.Join() 不起作用,因为我想在父线程中间等待集体完成,而不是在最后。

        使用Task.Run()Task.Factory.StartNew(),要么所有子线程相互阻塞,要么父线程不会被阻塞,...我不知道如何使用async 委托,因为await 语法的重新序列化。

        这是我使用线程而不是任务的解决方案:

        using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.ManualReset))
        {
          int outdex = mediaServerMinConnections - 1;
          for (int i = 0; i < mediaServerMinConnections; i++)
          {
            new Thread(() =>
            {
              sshPool.Enqueue(new SshHandler());
              if (Interlocked.Decrement(ref outdex) < 1)
                wh.Set();
            }).Start();
          }
          wh.WaitOne();
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-04-09
          • 1970-01-01
          • 2021-06-24
          • 1970-01-01
          • 2010-09-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多