有没有更好的方法来等待排队的线程?

时间:2009-06-25 20:12:21

标签: c# multithreading queue

在执行另一个进程之前是否有更好的方法等待排队的线程?

目前我在做:

this.workerLocker = new object(); // Global variable
this.RunningWorkers = arrayStrings.Length; // Global variable

// Initiate process

foreach (string someString in arrayStrings)
{
     ThreadPool.QueueUserWorkItem(this.DoSomething, someString);
     Thread.Sleep(100);
}

// Waiting execution for all queued threads
lock (this.workerLocker)  // Global variable (object)
{
     while (this.RunningWorkers > 0)
     {
          Monitor.Wait(this.workerLocker);
     }
}

// Do anything else    
Console.WriteLine("END");

// Method DoSomething() definition
public void DoSomething(object data)
{
    // Do a slow process...
    .
    .
    .

    lock (this.workerLocker)
    {
        this.RunningWorkers--;
        Monitor.Pulse(this.workerLocker);
    }
}

8 个答案:

答案 0 :(得分:14)

您可能想看一下AutoResetEvent和ManualResetEvent。

这些就是针对这种情况(在做“某事”之前等待ThreadPool线程完成)。

你会做这样的事情:

static void Main(string[] args)
{
    List<ManualResetEvent> resetEvents = new List<ManualResetEvent>();
    foreach (var x in Enumerable.Range(1, WORKER_COUNT))
    {
        ManualResetEvent resetEvent = new ManualResetEvent();
        ThreadPool.QueueUserWorkItem(DoSomething, resetEvent);
        resetEvents.Add(resetEvent);
    }

    // wait for all ManualResetEvents
    WaitHandle.WaitAll(resetEvents.ToArray()); // You probably want to use an array instead of a List, a list was just easier for the example :-)
}

public static void DoSomething(object data)
{
    ManualResetEvent resetEvent = data as ManualResetEvent;

    // Do something

    resetEvent.Set();
}

编辑:忘记提及你可以等待单个线程,任何线程等等。 另外,根据您的情况,AutoResetEvent可以简化一些事情,因为它(顾名思义)可以自动发出事件信号: - )

答案 1 :(得分:13)

仅使用Fork; {p

JoinMonitor怎么样?
Forker p = new Forker();
foreach (var obj in collection)
{
    var tmp = obj;
    p.Fork(delegate { DoSomeWork(tmp); });
}
p.Join();

earlier answer上显示的完整代码。

或者对于上限大小的生产者/消费者队列(线程安全等),here

答案 2 :(得分:4)

除了Barrier之外,Henk Holterman指出(BTW他是非常错误使用Barrier,请参阅我对他的回答的评论),.NET 4.0提供了大量其他选项(至在.NET 3.5中使用它们,您需要下载an extra DLL from Microsoft)。我写了一篇帖子that lists them all,但我最喜欢的是Parallel.ForEach:

Parallel.ForEach<string>(arrayStrings, someString =>
{
    DoSomething(someString);
});

在幕后,Parallel.ForEach排队到新的和改进的线程池,并等待所有线程完成。

答案 3 :(得分:3)

当我必须等待任务完成时,我真的很喜欢Begin- End- Async Pattern

我建议你将BeginEnd包装在一个worker类中:

public class StringWorker
{
    private string m_someString;
    private IAsyncResult m_result;

    private Action DoSomethingDelegate;

    public StringWorker(string someString)
    {
        DoSomethingDelegate = DoSomething;
    }

    private void DoSomething()
    {
        throw new NotImplementedException();
    }

    public IAsyncResult BeginDoSomething()
    {
        if (m_result != null) { throw new InvalidOperationException(); }
        m_result = DoSomethingDelegate.BeginInvoke(null, null);
        return m_result;
    }

    public void EndDoSomething()
    {
        DoSomethingDelegate.EndInvoke(m_result);
    }
}

要开始使用此代码段,请执行此操作:

List<StringWorker> workers = new List<StringWorker>();

foreach (var someString in arrayStrings)
{
    StringWorker worker = new StringWorker(someString);
    worker.BeginDoSomething();
    workers.Add(worker);
}

foreach (var worker in workers)
{
    worker.EndDoSomething();
}

Console.WriteLine("END");

就是这样。

旁注:如果您想从BeginEnd返回结果,请将“Action”更改为Func并更改EndDoSomething以返回类型。

public class StringWorker
{
    private string m_someString;
    private IAsyncResult m_result;

    private Func<string> DoSomethingDelegate;

    public StringWorker(string someString)
    {
        DoSomethingDelegate = DoSomething;
    }

    private string DoSomething()
    {
        throw new NotImplementedException();
    }

    public IAsyncResult BeginDoSomething()
    {
        if (m_result != null) { throw new InvalidOperationException(); }
        m_result = DoSomethingDelegate.BeginInvoke(null, null);
        return m_result;
    }

    public string EndDoSomething()
    {
        return DoSomethingDelegate.EndInvoke(m_result);
    }
}

答案 4 :(得分:3)

是的,有。

建议的方法

1)计数器和等待句柄

int ActiveCount = 1; // 1 (!) is important
EventWaitHandle ewhAllDone = new EventWaitHandle(false, ResetMode.Manual);

2)添加循环

foreach (string someString in arrayStrings)
{
     Interlocked.Increment(ref ActiveCount);

     ThreadPool.QueueUserWorkItem(this.DoSomething, someString);
     // Thread.Sleep(100); // you really need this sleep ?
}

PostActionCheck();
ewhAllDone.Wait();

3)DoSomething应该看起来像

{
    try
    {
        // some long executing code
    }
    finally
    {
        // ....
        PostActionCheck();
    }
} 

4)其中PostActionCheck

void PostActionCheck()
{
    if (Interlocked.Decrement(ref ActiveCount) == 0)
        ewhAllDone.Set();
}

ActiveCount初始化为1,然后递增n次。

PostActionCheck被称为n + 1次。最后一个将触发事件。

此解决方案的好处是使用单个内核对象(这是一个事件),以及轻量级API的2 * n + 1调用。 (可以减少吗?)

P.S。

我在这里写了代码,我可能拼错了一些类名。

答案 5 :(得分:2)

.NET 4.0有一个新类,Barrier

除此之外,您的方法并不是那么糟糕,如果在减量后RunningWorkers为0,您可以仅通过Pulsing进行优化。那看起来像是:

this.workerLocker = new object(); // Global variable
this.RunningWorkers = arrayStrings.Length; // Global variable

foreach (string someString in arrayStrings)
{
     ThreadPool.QueueUserWorkItem(this.DoSomething, someString);
     //Thread.Sleep(100);
}

// Waiting execution for all queued threads
Monitor.Wait(this.workerLocker);

// Method DoSomething() definition
public void DoSomething(object data)
{
    // Do a slow process...
    // ...

    lock (this.workerLocker)
    {
        this.RunningWorkers--;
        if (this.RunningWorkers == 0)
           Monitor.Pulse(this.workerLocker);
    }
}

您可以使用EventWaithandle或AutoResetEvent,但它们都包含在Win32 API中。由于Monitor类是纯托管代码,因此我更倾向于监视器。

答案 6 :(得分:0)

我不确定确实存在,我最近做了类似扫描子网的每个IP以接受特定端口的事情。

我可以提出一些可以提高性能的事情:

  • 使用ThreadPool的SetMaxThreads方法来调整性能(即平衡有大量线程一次运行,而不是在如此大量的线程上锁定时间)。

  • 在设置所有工作项时不要睡觉,没有真正的需要(我立刻意识到。但是在DoSomething方法中睡觉,可能只有一毫秒,以允许其他线程如果需要,可以跳进去。

我确信您可以自己实现更加自定义的方法,但我怀疑它比使用ThreadPool更有效。

P.S我不是100%清楚使用显示器的原因,因为你还在锁定?请注意,问题只是因为我之前没有使用过Monitor类,而不是因为我实际上怀疑它是否正在使用。

答案 7 :(得分:0)

使用Spring Threading。它内置了Barrier实现。

http://www.springsource.org/extensions/se-threading-net