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

Posted

技术标签:

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

如何?

【问题讨论】:

【参考方案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("Thread0 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();

【讨论】:

以上是关于创建多个线程并等待所有线程完成的主要内容,如果未能解决你的问题,请参考以下文章

pthread_join - 多个线程等待

多个线程等待所有线程完成,直到新工作开始

通过等待条件等待多个正在运行的线程

如何等待多个线程完成?

.Net最简单的创建多线程主线程等待所有线程执行完成的例子

Kotlin Coroutines:等待多个线程完成