C# 与BlockingCollection集成时Parallel.ForEach暂停

C# 与BlockingCollection集成时Parallel.ForEach暂停,c#,.net,parallel-processing,task-parallel-library,C#,.net,Parallel Processing,Task Parallel Library,我采用了基于中代码的并行/消费者实现 class ParallelConsumer:IDisposable { 私有只读int_maxParallel; 私有只读操作\u操作; 私有只读任务工厂_factory=new TaskFactory(); 私有取消令牌源\u令牌源; 私有只读BlockingCollection _entries=新BlockingCollection(); 私人任务(u Task),; 公共并行消费程序(int-maxpallel,Action-Action) { _

我采用了基于中代码的并行/消费者实现

class ParallelConsumer:IDisposable
{
私有只读int_maxParallel;
私有只读操作\u操作;
私有只读任务工厂_factory=new TaskFactory();
私有取消令牌源\u令牌源;
私有只读BlockingCollection _entries=新BlockingCollection();
私人任务(u Task),;
公共并行消费程序(int-maxpallel,Action-Action)
{
_maxParallel=maxParallel;
_行动=行动;
}
公开作废开始()
{
尝试
{
_tokenSource=新的CancellationTokenSource();
_任务=_factory.StartNew(
() =>
{
并行ForEach(
_entries.GetConsumingEnumerable(),
新的并行选项{MaxDegreeOfParallelism=\u maxParallel,CancellationToken=\u tokenSource.Token},
(项目,循环状态)=>
{
日志(“获取”+项目);
如果(!\u tokenSource.IsCancellationRequested)
{
_行动(项目);
日志(“完成”+项);
}
其他的
{
日志(“不带”+项目);
_entries.CompleteAdding();
loopState.Stop();
}
});
},
_tokenSource.Token);
}
捕获(操作取消异常oce)
{
系统诊断调试写线(oce);
}
}
私有无效日志(字符串消息)
{
控制台写入线(消息);
}
公共停车场()
{
处置();
}
公共无效排队(T条目)
{
日志(“排队”+进入);
_条目。添加(条目);
}
公共空间处置()
{
如果(_task==null)
{
返回;
}
_tokenSource.Cancel();
而(!\u任务已取消)
{
}
_task.Dispose();
_tokenSource.Dispose();
_task=null;
}
}
这是一个测试代码

class Program
{
    static void Main(string[] args)
    {
        TestRepeatedEnqueue(100, 1);
    }

    private static void TestRepeatedEnqueue(int itemCount, int parallelCount)
    {
        bool[] flags = new bool[itemCount];
        var consumer = new ParallelConsumer<int>(parallelCount,
                                              (i) =>
                                              {
                                                  flags[i] = true;
                                              }
            );
        consumer.Start();
        for (int i = 0; i < itemCount; i++)
        {
            consumer.Enqueue(i);
        }
        Thread.Sleep(1000);
        Debug.Assert(flags.All(b => b == true));



    }
}
类程序
{
静态void Main(字符串[]参数)
{
testrepeatedqueue(100,1);
}
私有静态void TestRepeatedQueue(int itemCount、int parallelCount)
{
bool[]标志=新bool[itemCount];
var消费者=新的ParallelConsumer(parallelCount,
(i) =>
{
flags[i]=true;
}
);
consumer.Start();
对于(int i=0;ib==true));
}
}

测试总是失败-它总是停留在100个测试项目的第93个左右。你知道我的代码的哪一部分导致了这个问题,以及如何修复它吗?

正如你所发现的,你不能将
Parallel.Foreach()
BlockingCollection.getconsumineGenumerable()
一起使用

有关说明,请参阅以下博客:

博客摘录:

BlockingCollection的GetConsumingEnumerable实现使用BlockingCollection的内部同步,该同步已经同时支持多个使用者,但ForEach不知道这一点,并且其可枚举分区逻辑在访问可枚举对象时也需要锁定

因此,这里的同步比实际需要的要多,从而导致潜在的不可忽略的性能损失

[另外]Parallel.ForEach和PLINQ默认使用的分区算法都使用分块来最小化同步成本:它不会对每个元素使用一次锁,而是使用锁,抓取一组元素(一个分块),然后释放锁

虽然这种设计有助于提高总体吞吐量,但对于更关注低延迟的场景,分块可能会让人望而却步

该博客还提供了名为
GetConsumingPartitioner()
的方法的源代码,您可以使用该方法解决问题

public static class BlockingCollectionExtensions
{

    public static Partitioner<T> GetConsumingPartitioner<T>(this BlockingCollection<T> collection)
    {
        return new BlockingCollectionPartitioner<T>(collection);
    }


    public class BlockingCollectionPartitioner<T> : Partitioner<T>
    {
        private BlockingCollection<T> _collection;

        internal BlockingCollectionPartitioner(BlockingCollection<T> collection)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            _collection = collection;
        }

        public override bool SupportsDynamicPartitions
        {
            get { return true; }
        }

        public override IList<IEnumerator<T>> GetPartitions(int partitionCount)
        {
            if (partitionCount < 1)
                throw new ArgumentOutOfRangeException("partitionCount");

            var dynamicPartitioner = GetDynamicPartitions();
            return Enumerable.Range(0, partitionCount).Select(_ => dynamicPartitioner.GetEnumerator()).ToArray();
        }

        public override IEnumerable<T> GetDynamicPartitions()
        {
            return _collection.GetConsumingEnumerable();
        }

    }
}
公共静态类阻止CollectionExtensions
{
公共静态分区器GetConsumingPartitioner(此BlockingCollection集合)
{
返回新的BlockingCollectionPartitioner(集合);
}
公共类阻止CollectionPartitioner:Partitioner
{
私人封锁收集(u收集);;
内部BlockingCollectionPartitioner(BlockingCollection集合)
{
if(集合==null)
抛出新的ArgumentNullException(“集合”);
_收集=收集;
}
公共覆盖布尔支持动态分区
{
获取{return true;}
}
公共覆盖IList GetPartitions(int partitionCount)
{
如果(分区计数<1)
抛出新ArgumentOutOfRangeException(“partitionCount”);
var dynamicPartitioner=GetDynamicPartitions();
返回Enumerable.Range(0,partitionCount).Select(=>dynamicPartitioner.GetEnumerator()).ToArray();
}
公共重写IEnumerable GetDynamicPartitions()
{
return _collection.getconsumineGenumerable();
}
}
}

故障原因如下所述:

两者默认使用的分区算法 Parallel.ForEach和PLINQ使用分块以最小化 同步成本:而不是
public static class BlockingCollectionExtensions
{

    public static Partitioner<T> GetConsumingPartitioner<T>(this BlockingCollection<T> collection)
    {
        return new BlockingCollectionPartitioner<T>(collection);
    }


    public class BlockingCollectionPartitioner<T> : Partitioner<T>
    {
        private BlockingCollection<T> _collection;

        internal BlockingCollectionPartitioner(BlockingCollection<T> collection)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            _collection = collection;
        }

        public override bool SupportsDynamicPartitions
        {
            get { return true; }
        }

        public override IList<IEnumerator<T>> GetPartitions(int partitionCount)
        {
            if (partitionCount < 1)
                throw new ArgumentOutOfRangeException("partitionCount");

            var dynamicPartitioner = GetDynamicPartitions();
            return Enumerable.Range(0, partitionCount).Select(_ => dynamicPartitioner.GetEnumerator()).ToArray();
        }

        public override IEnumerable<T> GetDynamicPartitions()
        {
            return _collection.GetConsumingEnumerable();
        }

    }
}
    public void StopAdding()
    {
        _entries.CompleteAdding();
    }
        consumer.Start();
        for (int i = 0; i < itemCount; i++)
        {
            consumer.Enqueue(i);
        }
        consumer.StopAdding();