我一直在使用Parallel.ForEach对项目集合进行一些耗时的处理。该处理实际上是由外部命令行工具处理的,我无法更改它。但是,似乎Parallel.ForEach会“卡在”集合中长期运行的项目上。我已经将问题简化了下来,可以证明Parallel.ForEach实际上正在等待这么长时间完成,并且不允许任何其他人通过。我已经编写了一个控制台应用程序来演示该问题:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace testParallel
{
class Program
{
static int inloop = 0;
static int completed = 0;
static void Main(string[] args)
{
// initialize an array integers to hold the wait duration (in milliseconds)
var items = Enumerable.Repeat(10, 1000).ToArray();
// set one of the items to 10 seconds
items[50] = 10000;
// Initialize our line for reporting status
Console.Write(0.ToString("000") + " Threads, " + 0.ToString("000") + " completed");
// Start the loop in a task (to avoid SO answers having to do with the Parallel.ForEach call, itself, not being parallel)
var t = Task.Factory.StartNew(() => Process(items));
// Wait for the operations to compelte
t.Wait();
// Report finished
Console.WriteLine("\nDone!");
}
static void Process(int[] items)
{
// SpinWait (not sleep or yield or anything) for the specified duration
Parallel.ForEach(items, (msToWait) =>
{
// increment the counter for how many threads are in the loop right now
System.Threading.Interlocked.Increment(ref inloop);
// determine at what time we shoule stop spinning
var e = DateTime.Now + new TimeSpan(0, 0, 0, 0, msToWait);
// spin until the target time
while (DateTime.Now < e) /* no body -- just a hard loop */;
// count another completed
System.Threading.Interlocked.Increment(ref completed);
// we're done with this iteration
System.Threading.Interlocked.Decrement(ref inloop);
// report status
Console.Write("\r" + inloop.ToString("000") + " Threads, " + completed.ToString("000") + " completed");
});
}
}
}
基本上,我创建一个int数组来存储给定操作花费的毫秒数。我将它们全部设置为10,但其中一个设置为10000(即10秒)。我在任务中启动了Parallel.ForEach并在硬旋转等待中处理每个整数(因此它不应屈服或睡眠或其他任何事情)。 在每次迭代中,我报告当前循环主体中 的迭代次数,以及我们已完成的迭代次数。通常情况下,一切都很好。但是,到最后(按时间),它报告“ 001线程,987已完成”。
我的问题是,为什么它不使用其他7个核心来处理其余13个“任务”?这个长时间运行的迭代不应该阻止它处理集合中的其他元素,对吧?
此示例碰巧是固定集合,但可以轻松将其设置为可枚举。我们不想仅仅因为花费了很长时间而停止获取该枚举中的下一个项目。
答案 0 :(得分:0)
我找到了答案(或者至少是一个答案)。它与块分区有关。 SO的答案here为我找到了答案。所以基本上,如果我对此进行更改,则在“处理”功能的顶部:
static void Process(int[] items)
{
Parallel.ForEach(items, (msToWait) => { ... });
}
对此
static void Process(int[] items)
{
var partitioner = Partitioner.Create(items, EnumerablePartitionerOptions.NoBuffering);
Parallel.ForEach(partitioner, (msToWait) => { ... });
}
它一次抓取一件作品。对于每一个平行的更典型的情况,人体不超过一秒钟,我当然可以看到将工作集分块。但是,在我的用例中,每个身体部位可能要花费半秒到5个小时。我当然不希望一堆5小时的元素会阻塞10秒的各种元素。因此,在这种情况下,“一次一次”的开销是值得的。