C# AsParallel扩展实际上是如何工作的

C# AsParallel扩展实际上是如何工作的,c#,.net,linq,task-parallel-library,plinq,C#,.net,Linq,Task Parallel Library,Plinq,所以话题就是问题 我得到的方法是AsParallel返回使用相同LINQ关键字的包装器ParallelQuery,但是来自System.LINQ.ParallelEnumerable,而不是System.LINQ.Enumerable 这很清楚,但当我查看反编译的源代码时,我不明白它是如何工作的 让我们从一个最简单的扩展开始:Sum()方法。代码: [__DynamicallyInvokable] public static int Sum(this ParallelQuery<int&g

所以话题就是问题

我得到的方法是AsParallel返回使用相同LINQ关键字的包装器
ParallelQuery
,但是来自
System.LINQ.ParallelEnumerable
,而不是
System.LINQ.Enumerable

这很清楚,但当我查看反编译的源代码时,我不明白它是如何工作的

让我们从一个最简单的扩展开始:Sum()方法。代码:

[__DynamicallyInvokable]
public static int Sum(this ParallelQuery<int> source)
{
  if (source == null)
    throw new ArgumentNullException("source");
  else
    return new IntSumAggregationOperator((IEnumerable<int>) source).Aggregate();
}

问题是:它是如何工作的?对于一个被许多线程修改的变量,我看不到并发安全性,我们只看到迭代器和求和。是神奇的枚举器吗?或者它是如何工作的
GetEnumerator()
返回
queryOpenEnumerator
,但它的代码太复杂了

Sum操作符将所有值聚合到一个线程中。这里没有多线程。诀窍在于多线程正在其他地方发生

PLINQ
Sum
方法可以处理PLINQ枚举。可以使用允许在多个线程上处理集合的其他构造(例如where)构建这些可枚举项

求和运算符始终是链中的最后一个运算符。虽然可以在多个线程上处理这个总和,但TPL团队可能发现这对性能有负面影响,这是合理的,因为这个方法唯一要做的就是简单的整数相加

因此,该方法处理来自其他线程的所有结果,并在单个线程上处理这些结果,然后返回该值。真正的诀窍在于其他PLINQ扩展方法。

受保护的override int InternalAggregate(参考异常singularExceptionToThrow)
protected override int InternalAggregate(ref Exception singularExceptionToThrow)
{
  using (IEnumerator<int> enumerator = this.GetEnumerator(new ParallelMergeOptions?    (ParallelMergeOptions.FullyBuffered), true))
  {
    int num = 0;
    while (enumerator.MoveNext())
      checked { num += enumerator.Current; }
    return num;
  }
}
{ 使用(IEnumerator enumerator=this.GetEnumerator(新的ParallelMergeOptions?(ParallelMergeOptions.FullyBuffered),true)) { int num=0; while(枚举数.MoveNext()) 选中{num+=enumerator.Current;} 返回num; } }
此代码不会并行执行,而将在其内部范围内顺序执行

试试这个

        List<int> list = new List<int>();

        int num = 0;

        Parallel.ForEach(list, (item) =>
            {
                checked { num += item; }
            });
List List=新列表();
int num=0;
Parallel.ForEach(列表,(项目)=>
{
选中{num+=item;}
});
内部操作将分布在线程池中,当处理所有项时,ForEach语句将完成

在这里,您需要安全:

        List<int> list = new List<int>();

        int num = 0;

        Parallel.ForEach(list, (item) =>
            {
                Interlocked.Add(ref num, item);
            });
List List=新列表();
int num=0;
Parallel.ForEach(列表,(项目)=>
{
联锁。添加(参考编号,项目);
});
在第二次PLINQ攻击中,我终于找到了答案。这很清楚。 问题是枚举器并不简单。这是一种特殊的
多线程
方法。那么它是如何工作的呢?答案是,
enumerator
不返回source的下一个值,它返回下一个分区的整和。所以这个代码只在2,4,6,8执行。。。在
枚举器中执行实际求和工作时的次数(基于
环境.ProcessorCount
),在
枚举器.OpenQuery
方法中移动下一步


因此,TPL明显地对源可枚举项进行了分区,然后对每个分区进行独立求和,然后对该求和进行重新计算,请参见
IntSumAggregationOperatorEnumerator
。这里没有什么神奇的地方,只是可以更深一层。

不是我的代码,它是.Net Framework 4.5的反编译源代码,当然它可以工作,但它不是多线程的。它被铸造成一个数字?IntSumAggregationOperator((IEnumerable)source So仍然是按顺序执行的。我认为没有任何理由对多线程进行求和,因为它们都会影响结果。没有装箱不能成为3x性能差异的原因。这个答案中的代码是同步完成的全部工作,因此最终结果是它的性能比单线程差解决方案。在多核环境中加速求和的方法是将输入划分为多个批次,并行求和这些批次,然后组合这些批次的结果。请注意,这可能有多个级别,其中每个批次被分解为子批次,并并行运行,依此类推。答案是关于多线程,InternalAggregate没有处理多线程的并行查询。所以我举了一个例子。当然这会比较慢。如果“这里没有多线程”为什么sum2的计算速度比sum1快3倍?@AlexJoukovsky:我测试过这个,而且
AsParallel
版本确实更快,但我不知道这是怎么可能的,特别是因为输入枚举也可以一次在一个线程上处理。奇怪。部分和可以并行处理,然后合并。@usr:这是真的e、 但是正如Alex所展示的,当前的PLINQ Sum实现并没有并行处理这个问题。我没有看到Sum1和Sum2?
        List<int> list = new List<int>();

        int num = 0;

        Parallel.ForEach(list, (item) =>
            {
                Interlocked.Add(ref num, item);
            });