如何在n个线程完成工作之前停止一个线程

时间:2009-12-30 16:14:36

标签: .net multithreading .net-3.5 synchronization locking

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

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

关于如何做到这一点的任何想法?

提前致谢。

10 个答案:

答案 0 :(得分: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 :(得分:2)

执行与垃圾收集类似的操作。您将编写一个ThreadManager,其中包含正在运行的线程数。当主线程启动新工作程序时,ThreadManager将增加其工作计数。当一个worker完成时,它将通知ThreadManager谁将减少它的线程数。当它具有零工作线程时,ThreadManager将唤醒主线程。

答案 2 :(得分:2)

似乎WaitHandle.WaitAll应该可以解决这个问题。

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

如果工作线程循环或需要多次“脉冲”,您可以使用AutoResetEvents,如下所示:

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

如果没有(如果主线程只需要知道工作线程已经超过某个阈值),ManualResetEvents就可以了。

使用WaitAll时需要注意一些事项(来自MSDN WaitAll文档):

  

在某些实现中,如果超过   传递64个句柄,a   抛出NotSupportedException。如果   数组包含重复项   呼叫失败了   DuplicateWaitObjectException。

但是,一个进程很少能够真正利用超过64个线程,因此这个限制通常不会很重要。

答案 3 :(得分:1)

它被称为障碍:http://programmingexamples.wikidot.com/java-barrier

Ow,但是如果你只需要第一个线程等待其余部分通过某个点并且你希望另一个继续工作,那么使用大小为N的信号量并让所有其他线程接受它,而第一个线程等待在它们之后获取它..

信号量:http://programmingexamples.wikidot.com/java-semaphore

答案 4 :(得分:1)

如果你只需要等到线程终止,Thread.Join怎么样?在.NET 4.0中,您可以使用Task.WaitAll。如果你需要等到他们完成任务的一部分,那就有点蠢事。在当前版本的.NET中,请查看WaitHandle.WaitAll / Threading.ManualResetEvent。在.NET 4.0中,您可以使用Threading.Barrier

答案 5 :(得分: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());

答案 6 :(得分: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中的任何线程都不会执行Stop操作:

SyncLock (DirectCast(m_waitHandles, IList).SyncRoot)
    If m_waitHandles.Count > 0 Then
        Threading.WaitHandle.WaitAll(m_waitHandles.ToArray())
    End If
End SyncLock

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

感谢所有人,伙伴们!

答案 7 :(得分:1)

尝试使用:

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

在每个帖子中:

// Do task

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

主线程:

completedEvent.WaitOne();

答案 8 :(得分: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();
    }
}

答案 9 :(得分: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();
    }
}