我有要处理的批次列表。永远。
我想并行完成每个块(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.
}
}
答案 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);
}
}
}