C#Parallel.ForEach在长迭代中阻塞

C#Parallel.ForEach在长迭代中阻塞,c#,blocking,parallel.foreach,C#,Blocking,Parallel.foreach,我一直在使用Parallel.ForEach对项目集合进行一些耗时的处理。处理实际上是由一个外部命令行工具来处理的,我不能改变这一点。但是,Parallel.ForEach似乎会被集合中的一个长时间运行的项“卡住”。我已经将问题提炼出来,可以证明Parallel.ForEach实际上是在等待这个长问题完成,而不允许任何其他问题通过。我已经编写了一个控制台应用程序来演示这个问题: using System; using System.Collections.Generic; using Syste

我一直在使用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");

            });
        }
    }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用系统文本;
使用System.Threading.Tasks;
命名空间testParallel
{
班级计划
{
静态int inloop=0;
静态int completed=0;
静态void Main(字符串[]参数)
{
//初始化数组整数以保持等待持续时间(以毫秒为单位)
var items=Enumerable.Repeat(101000).ToArray();
//将其中一项设置为10秒
项目[50]=10000;
//为报告状态初始化我们的行
Console.Write(0.ToString(“000”)+“线程,+0.ToString(“000”)+“已完成”);
//在任务中启动循环(以避免答案与Parallel.ForEach调用本身有关,而不是并行)
var t=Task.Factory.StartNew(()=>Process(items));
//等待操作完成
t、 等待();
//报告完成
Console.WriteLine(“\nDone!”);
}
静态作废流程(int[]项)
{
//在指定的持续时间内进行SpinWait(非睡眠、屈服或任何操作)
Parallel.ForEach(项目,(msToWait)=>
{
//增加当前循环中有多少线程的计数器
系统。螺纹。联锁。增量(参考inloop);
//确定什么时候停止旋转
var e=DateTime.Now+新的时间跨度(0,0,0,0,msToWait);
//旋转到目标时间
而(DateTime.Now
基本上,我创建了一个int数组来存储给定操作所需的毫秒数。我将它们全部设置为10,除了一个,我将其设置为10000(因此,10秒)。我在一个任务中启动Parallel.ForEach,并在一个硬旋转等待中处理每个整数(因此它不应该是屈服的、休眠的或任何事情)。 在每次迭代中,我报告现在循环体中有多少次迭代,以及我们完成了多少次迭代。大多数情况下,进展顺利。然而,在接近结束时(时间方面),它报告“001个线程,987个已完成”

我的问题是,为什么它不使用其他7个内核来处理其余13个“作业”?这个长期运行的迭代不应该阻止它处理集合中的其他元素,对吗

这个示例恰好是一个固定集合,但可以轻松地将其设置为可枚举集合。我们不会因为一项花费了很长时间而停止获取枚举表中的下一项。

我找到了答案(或者至少找到了答案)。它与块分区有关。所以我得到了答案。因此,基本上,在我的“流程”功能的顶部,如果我从以下内容更改:

        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小时的时间。我当然不希望一堆10秒的综艺元素被一个5小时的元素所阻挡。因此,在这种情况下,“一次一个”的开销是非常值得的。

如果有人有兴趣进一步挖掘,请查看我的兴趣,因为我可以复制您在问题中发布的确切结果。当我修改代码,将
字典
用于
时,我看到了一个不同的改进结果,长时间运行的任务只耽搁了一个额外的任务。以下说明了原因: