创建多个线程并等待所有线程完成
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();
【讨论】:
以上是关于创建多个线程并等待所有线程完成的主要内容,如果未能解决你的问题,请参考以下文章