ManualResetEvent不等待线程池完成

时间:2015-04-06 09:38:20

标签: c# multithreading threadpool manualresetevent

我有要处理的批次列表。永远。
我想并行完成每个块(5),当它完成后移动到下一个块 由于某种原因,下面的代码不等待块完成并继续,即使它没有完成。

while (true)
{
    foreach (string[] urlsArr in chunks)
    { 
        int i = 0;
        foreach (var url in urlsArr)
        {
            ThreadPool.QueueUserWorkItem(x =>
            {
                ProccessUrl(url, config, drivers[i]);
                _resetEvent.Set();
                i++;
            });
        }
        _resetEvent.WaitOne();// this is not really waiting.
    }
}

3 个答案:

答案 0 :(得分:1)

查看Semaphore或它的超薄版本。信号量将允许您始终只运行5个线程。一旦这些运行线程中的任何一个完成,它就可以获得新的工作。这样更有效,尤其是在工作量不均匀的情况下。考虑当1个项目需要一个小时来处理而另外4个需要一秒钟时的情况。在这种情况下,4个线程将在完成任何其他工作之前等待最后一个完成。

有关示例,请参阅Need to understand the usage of SemaphoreSlim

在您的代码中,问题是您只有一个等待句柄和5个线程。当5个正在运行的线程中的任何一个完成工作时,它将设置等待句柄,从而允许你的外部循环继续,这将启动另外五个线程。到目前为止,内循环的前4个线程可能已经完成,其中任何一个都可以再次设置等待句柄!现在,你觉得这里有问题吗?

根据Hans评论,如果一个批次中的工作项之间存在依赖关系,那么必须先完成所有工作项才能开始下一个批处理,您应该查看CountDownEvent < / p>

答案 1 :(得分:1)

这是一个带有Tasks(async / await)

的版本
while (true)
        {
            foreach (string[] urlsArr in chunks)
            {
                Task[] tasks = new Task[urlsArr.Length];
                for (int i = 0; i < urlsArr.Length; i++)
                {
                    var url = urlsArr[i];
                    var driver = drivers[i];
                    tasks[i] = Task.Run(() => { ProccessUrl(url, config, driver); });
                }

                await Task.WhenAll(tasks);
            }
        }

请注意,它还修复了“我”的问题。原始代码中的变量没有以线程安全的方式递增(可以使用Interlocked.Increment修复)。

如果您的代码不是async,您可以等待线程中的任务完成(但这是阻止的)

Task.WhenAll(tasks).Wait();

答案 2 :(得分:0)

我认为您可以简化整个事情,并利用Parallel.ForEach()来管理线程并将并发度限制为5。

如果运行以下示例代码,您将看到假装URL正在以5的块处理,因为并发线程的数量限制为5个。

如果你这样做,你将不需要你自己的分块逻辑:

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main()
        {
            // Make some pretend URLs for this demo.

            string[] urls = Enumerable.Range(1, 100).Select(n => n.ToString()).ToArray();

            // Use Parallel.ForEach() along with MaxDegreeOfParallelism = 5 to process
            // these using 5 threads maximum:

            Parallel.ForEach(
                urls,
                new ParallelOptions{MaxDegreeOfParallelism = 5},
                processUrl
            );
        }

        static void processUrl(string url)
        {
            Console.WriteLine("Processing " + url);
            Thread.Sleep(1000);
            Console.WriteLine("Processed " + url);
        }
    }
}