2009-12-30 58 views
4

我有一个主线程的应用程序和N工作线程。在某些时候,我需要主线程等待,直到所有的线程完成其工作的一部分。如何停止一个线程,直到n个线程完成其工作

我通常会使用Monitor.Wait()和Monitor.Pulse(),但这会阻止线程同时工作。

任何想法如何做到这一点?

在此先感谢。

+0

只是一个想法:你在下面的评论中描述,你想要表示某个部分已经通过,因为线程没有终止。这表示每个线程中有一个循环。您的行为是否定义为工作线程在循环中的第二次迭代,因为它们已经表明它们已完成? – 2009-12-30 16:54:00

+0

@San:不,它不是一个循环,它只是一个很长的操作,为了避免一些异常,在某个点之前无法停止,所以我需要锁定Stop方法,直到所有线程都通过该点。 – 2009-12-30 17:02:51

回答

1

好吧,我现在做(使用你的想法),并似乎工作是这样的:

我宣布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 

我不确定这是否以正确的方式完成,因为这是我第一次处理这样的事情。你觉得这是好的,是一个好方法吗?

感谢所有,配偶!

+1

很高兴我们可以帮助! – 2009-12-30 17:37:51

1

如果你只需要等待,直到线程终止,怎么样Thread.Join?在.NET 4.0中,您可以使用Task.WaitAll。如果你需要等到他们完成任务的一部分,这是一个小窍门。在当前版本的.NET中,请看WaitHandle.WaitAll/Threading.ManualResetEvent。在.NET 4.0中,您可以使用Threading.Barrier

+0

不,线程不会终止,只是在他们的代码中重复一点。 – 2009-12-30 16:21:39

+0

@SoMoS:查看MSDN上有关'WaitHandle.WaitAll'和'ManualResetEvent'的示例。 – jason 2009-12-30 17:10:15

2

做一些类似垃圾收集。您将编写一个ThreadManager,其中有多少个线程正在运行。当主线程启动一个新工作者时,ThreadManager将增加工作者的数量。当工作完成时,它会通知ThreadManager谁将递减其线程数。当它有零个工作线程时,ThreadManager将唤醒主线程。

+1

修改或读取时不要忘记锁定计数变量。 – 2009-12-30 16:51:13

8

.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... */ 
} 
+1

看起来像我需要的,但我现在需要在3.5。有一种方法可以做到这一点? – 2009-12-30 16:22:25

+0

@SoMoS,如果我明白你要做什么,你可以用'AutoResetEvents'(我在我的答案中描述)使用'WaitAll'来做你想要的。 – 2009-12-30 17:00:24

+0

@Jeff Sternal:我相信我们都提出了相同的解决方案,但有不同的实现。 – 2009-12-30 17:03:12

1

由于在一些实现,有多少把手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()); 
+0

为什么不使用'WaitHandle.WaitAll'? – 2009-12-30 16:35:14

+0

因为WaitAll有多少个句柄是有限制的......没有双关语意思)...句柄...从msdn中,“WaitAll方法在所有句柄被发信号时返回,在一些实现中,如果超过64句柄被传递时,抛出NotSupportedException异常。“ – 2009-12-30 16:46:39

+0

确实如此,但'foreach'方法不适用于'AutoResetEvents'(更确切地说,它会产生不可预知的结果,并且可能导致死锁)。 – 2009-12-30 17:12:15

2

好像WaitHandle.WaitAll应该解决这个问题。

您的主线程将需要保持对工作线程等待句柄的引用。当它需要同步时,将这些句柄传递给上述方法。工作线程在其代码中的适当位置线程发出信号。

如果工作线程循环或需要“脉冲”多次,你可以使用AutoResetEvents,像这样:

public void WorkerMethod() {   
    DoFirstThing(); 
    this.autoResetEvent.Set(); 
    DoSecondThing(); 
    this.autoResetEvent.Set(); 
    // etc. 
} 

如果没有(如果只需要在主线程知道工作者线程已经过去了一些门槛),ManualResetEvents会很好。

有几件事情需要提防使用了WaitAll时(从MSDN WaitAll文档)的:

在一些实施中,如果超过64 句柄传递,一个 NotSupportedException异常被抛出。如果 该阵列包含重复项,则 调用失败,并返回 DuplicateWaitObjectException。

但是,一个进程真的可以利用超过64个线程的优势很少,所以这个限制通常并不重要。

+0

我喜欢你的方法。我必须考虑到这是为了处理传入的tcp客户端,所以也许我有更多的64个线程(每个客户端一个)。我如何检查这个限制适用于哪里? – 2009-12-30 17:17:17

+0

不幸的是,这些信息很难得到。我可以在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

0

使用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(); 
    } 
} 
+0

我不等待其他线程终止,只是为了超过某个点。 – 2009-12-30 16:45:07

+0

@SoMoS:我明白。为什么不把它分成两个线程呢? :)等待第一个,然后离开第二个。 – serhio 2009-12-30 16:59:09

+0

在两个线程中分割工作看起来不太好,第一个在第二个线程结束时开始第二个。如果我需要检查2点而不是1点怎么办?无论如何谢谢队友! – 2009-12-30 17:05:19

1

尝试使用这样的:

int threadsCompleted = 0; 
int numberOfThreads = 4; 
ManualResetEvent completedEvent = new ManualResetEvent(false); 

在每个线程:

// Do task 

if (Interlocked.Increment(threadsCompleted) == numberOfThreads) 
    completedEvent.Set(); 

主线程:

completedEvent.WaitOne(); 
1

所有在互联网上的人尝试使用EventHandlesWaitAll()数组。我想出了以下的课程,这个课程在资源上要轻得多。我试图想到不同的比赛场景,我相信这段代码没有竞争条件。(在递减和检查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(); 
    } 
} 
+0

你是我想出的方法,直到类名。我的不同之处在于我有一个单独的释放活动线程的方法,以更好地控制哪些线程块。 – 2012-12-20 17:17:49

相关问题