我有一个主线程的应用程序和N工作线程。在某些时候,我需要主线程等待,直到所有的线程完成其工作的一部分。如何停止一个线程,直到n个线程完成其工作
我通常会使用Monitor.Wait()和Monitor.Pulse(),但这会阻止线程同时工作。
任何想法如何做到这一点?
在此先感谢。
我有一个主线程的应用程序和N工作线程。在某些时候,我需要主线程等待,直到所有的线程完成其工作的一部分。如何停止一个线程,直到n个线程完成其工作
我通常会使用Monitor.Wait()和Monitor.Pulse(),但这会阻止线程同时工作。
任何想法如何做到这一点?
在此先感谢。
好吧,我现在做(使用你的想法),并似乎工作是这样的:
我宣布ManualResetEvent的列表:
Private m_waitHandles As List(Of Threading.ManualResetEvent)
该进程接受传入的Tcp连接并在每个连接上启动一个线程。因此,在新的客户端处理程序我已经添加以下代码:
Dim waitHandle As Threading.ManualResetEvent
waitHandle = New Threading.ManualResetEvent(True)
SyncLock (DirectCast(m_waitHandles, IList).SyncRoot)
m_waitHandles.Add(waitHandle)
End SyncLock
''# Do all the work
StoppableMethod()
SyncLock (DirectCast(m_waitHandles, IList).SyncRoot)
waitHandle = m_waitHandles.Item(Threading.WaitHandle.WaitAny(m_waitHandles.ToArray()))
End SyncLock
waitHandle.Reset()
NonStoppableMethod()
waitHandle.Set()
SyncLock (DirectCast(m_waitHandles, IList).SyncRoot)
m_waitHandles.Remove(waitHandle)
End SyncLock
做的最后一件事是修改Stop方法,以确保停止操作不会与NonStoppableMethod内的任何线程中完成的:
SyncLock (DirectCast(m_waitHandles, IList).SyncRoot)
If m_waitHandles.Count > 0 Then
Threading.WaitHandle.WaitAll(m_waitHandles.ToArray())
End If
End SyncLock
我不确定这是否以正确的方式完成,因为这是我第一次处理这样的事情。你觉得这是好的,是一个好方法吗?
感谢所有,配偶!
很高兴我们可以帮助! – 2009-12-30 17:37:51
这就是所谓的一个障碍:http://programmingexamples.wikidot.com/java-barrier
嗷,但如果你只需要第一个线程等待其余通过某些时候,你想让对方依然继续工作,然后用大小为N的信号让所有其他线程需要它,而第一个线程等待后,他们获得它..
如果你只需要等待,直到线程终止,怎么样Thread.Join
?在.NET 4.0中,您可以使用Task.WaitAll
。如果你需要等到他们完成任务的一部分,这是一个小窍门。在当前版本的.NET中,请看WaitHandle.WaitAll
/Threading.ManualResetEvent
。在.NET 4.0中,您可以使用Threading.Barrier
。
不,线程不会终止,只是在他们的代码中重复一点。 – 2009-12-30 16:21:39
@SoMoS:查看MSDN上有关'WaitHandle.WaitAll'和'ManualResetEvent'的示例。 – jason 2009-12-30 17:10:15
做一些类似垃圾收集。您将编写一个ThreadManager,其中有多少个线程正在运行。当主线程启动一个新工作者时,ThreadManager将增加工作者的数量。当工作完成时,它会通知ThreadManager谁将递减其线程数。当它有零个工作线程时,ThreadManager将唤醒主线程。
修改或读取时不要忘记锁定计数变量。 – 2009-12-30 16:51:13
.NET 4.0将包括System.Threading.Barrier
类,它将使多个线程之间的同步更容易。有一些很好的示例代码的博客帖子可以找到here。
Similar functionality can be achieved在.NET 3.0+中使用多个WaitHandles,如在MSDN上的this example中所示。
MSDN的例子的小结:
const int numberOfWorkers = 5;
static void Main()
{
var handles = new ManualResetEvent[numberOfWorkers];
for (int i = 0; i < numberOfWorkers; i++)
{
handles[i] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(o => worker.Work(), null);
}
// Wait for all workers to finish before continuing
WaitHandle.WaitAll(handles);
/* continue execution... */
}
看起来像我需要的,但我现在需要在3.5。有一种方法可以做到这一点? – 2009-12-30 16:22:25
@SoMoS,如果我明白你要做什么,你可以用'AutoResetEvents'(我在我的答案中描述)使用'WaitAll'来做你想要的。 – 2009-12-30 17:00:24
@Jeff Sternal:我相信我们都提出了相同的解决方案,但有不同的实现。 – 2009-12-30 17:03:12
由于在一些实现,有多少把手WaitHandle.WaitAll()
可以....处理,(见msdn-WaitHandle.WaitAll()的限制,我创建了一个实用方法为此:
public static void WaitAll(WaitHandle[] handles)
{
if (handles == null)
throw new ArgumentNullException("handles",
"WaitHandle[] handles was null");
foreach (WaitHandle wh in handles) wh.WaitOne();
}
用法是添加等待句柄每个线程的阵列,然后调用上述工具方法(传递数组)的所有线程已经启动之后
。List<WaitHandle> waitHndls = new List<WaitHandle>();
foreach (MyType mTyp in MyTypeCollection)
{
ManualResetEvent txEvnt = new ManualResetEvent(false);
int qryNo1 = ++qryNo;
ThreadPool.QueueUserWorkItem(
delegate
{
try
{
// Code to execute whatever thread's function is...
}
catch (SomeCustomException iX)
{
// catch code
} }
finally { lock (locker) txEvnt.Set(); }
});
waitHndls.Add(txEvnt);
}
util.WaitAll(waitHndls.ToArray());
为什么不使用'WaitHandle.WaitAll'? – 2009-12-30 16:35:14
因为WaitAll有多少个句柄是有限制的......没有双关语意思)...句柄...从msdn中,“WaitAll方法在所有句柄被发信号时返回,在一些实现中,如果超过64句柄被传递时,抛出NotSupportedException异常。“ – 2009-12-30 16:46:39
确实如此,但'foreach'方法不适用于'AutoResetEvents'(更确切地说,它会产生不可预知的结果,并且可能导致死锁)。 – 2009-12-30 17:12:15
好像WaitHandle.WaitAll
应该解决这个问题。
您的主线程将需要保持对工作线程等待句柄的引用。当它需要同步时,将这些句柄传递给上述方法。工作线程在其代码中的适当位置线程发出信号。
如果工作线程循环或需要“脉冲”多次,你可以使用AutoResetEvents
,像这样:
public void WorkerMethod() {
DoFirstThing();
this.autoResetEvent.Set();
DoSecondThing();
this.autoResetEvent.Set();
// etc.
}
如果没有(如果只需要在主线程知道工作者线程已经过去了一些门槛),ManualResetEvents
会很好。
有几件事情需要提防使用了WaitAll时(从MSDN WaitAll
文档)的:
在一些实施中,如果超过64 句柄传递,一个 NotSupportedException异常被抛出。如果 该阵列包含重复项,则 调用失败,并返回 DuplicateWaitObjectException。
但是,一个进程真的可以利用超过64个线程的优势很少,所以这个限制通常并不重要。
我喜欢你的方法。我必须考虑到这是为了处理传入的tcp客户端,所以也许我有更多的64个线程(每个客户端一个)。我如何检查这个限制适用于哪里? – 2009-12-30 17:17:17
不幸的是,这些信息很难得到。我可以在Reflector中看到它被硬编码到了.NET 2.0实现中,但是手头没有3.5程序集。这里有一篇文章提出了一种巧妙的解决方案来克服极限,这与David Souther在另一个答案中提出的类似:创建自己的'WaitHandle'类来实现引用计数,以便在所有工作线程中共享一个'WaitHandle' 。 http://msdn.microsoft.com/en-us/magazine/cc163914.aspx。 – 2009-12-30 22:07:11
使用Thread.Join(即阻塞调用线程,直到某个线程终止,同时继续执行标准的COM和SendMessage消息泵)像实例方法:
using System;
using System.Threading;
class IsThreadPool
{
static void Main()
{
AutoResetEvent autoEvent = new AutoResetEvent(false);
Thread regularThread =
new Thread(new ThreadStart(ThreadMethod));
regularThread.Start();
ThreadPool.QueueUserWorkItem(new WaitCallback(WorkMethod),
autoEvent);
// __________ Wait for foreground thread to end. __________
regularThread.Join();
// Wait for background thread to end.
autoEvent.WaitOne();
}
static void ThreadMethod()
{
Console.WriteLine("ThreadOne, executing ThreadMethod, " +
"is {0}from the thread pool.",
Thread.CurrentThread.IsThreadPoolThread ? "" : "not ");
}
static void WorkMethod(object stateInfo)
{
Console.WriteLine("ThreadTwo, executing WorkMethod, " +
"is {0}from the thread pool.",
Thread.CurrentThread.IsThreadPoolThread ? "" : "not ");
// Signal that this thread is finished.
((AutoResetEvent)stateInfo).Set();
}
}
我不等待其他线程终止,只是为了超过某个点。 – 2009-12-30 16:45:07
@SoMoS:我明白。为什么不把它分成两个线程呢? :)等待第一个,然后离开第二个。 – serhio 2009-12-30 16:59:09
在两个线程中分割工作看起来不太好,第一个在第二个线程结束时开始第二个。如果我需要检查2点而不是1点怎么办?无论如何谢谢队友! – 2009-12-30 17:05:19
尝试使用这样的:
int threadsCompleted = 0;
int numberOfThreads = 4;
ManualResetEvent completedEvent = new ManualResetEvent(false);
在每个线程:
// Do task
if (Interlocked.Increment(threadsCompleted) == numberOfThreads)
completedEvent.Set();
主线程:
completedEvent.WaitOne();
所有在互联网上的人尝试使用EventHandles
和WaitAll()
数组。我想出了以下的课程,这个课程在资源上要轻得多。我试图想到不同的比赛场景,我相信这段代码没有竞争条件。(在递减和检查Count
上的条件之间存在理论上的竞争,但据我所知它不影响功能,并且代码将始终工作。)
要使用此类,需要同步的所有线程必须调用其方法Wait()
。他们将阻止,直到Count
线程数称为Wait()
。一个实例只能用于同步一次(它不能被重置)。
internal class ThreadBarrier
{
private ManualResetEvent BarrierEvent;
private int Count;
internal ThreadBarrier(int count)
{
BarrierEvent = new ManualResetEvent(false);
Count = count;
}
internal void Wait()
{
Interlocked.Decrement(ref Count);
if (Count > 0)
BarrierEvent.WaitOne();
else
BarrierEvent.Set();
}
}
你是我想出的方法,直到类名。我的不同之处在于我有一个单独的释放活动线程的方法,以更好地控制哪些线程块。 – 2012-12-20 17:17:49
只是一个想法:你在下面的评论中描述,你想要表示某个部分已经通过,因为线程没有终止。这表示每个线程中有一个循环。您的行为是否定义为工作线程在循环中的第二次迭代,因为它们已经表明它们已完成? – 2009-12-30 16:54:00
@San:不,它不是一个循环,它只是一个很长的操作,为了避免一些异常,在某个点之前无法停止,所以我需要锁定Stop方法,直到所有线程都通过该点。 – 2009-12-30 17:02:51