为什么Tasks提供不同的Linq Sum()输出?

时间:2018-02-26 20:05:46

标签: c# linq

下面的代码很奇怪,它给了我一个可枚举范围的Sum

为什么?

var num = Enumerable.Range(-10000, 20001).Reverse().ToList();

Action task1 = () => 
{
    Console.WriteLine($"Sum 2: {num.Sum()}");
};

Action task2 = () =>
{
    num.Sort();
    Console.WriteLine($"Sum 3: { num.Sum()}");
};

Parallel.Invoke(task1, task2);

Console.ReadLine();

预期输出:

Sum 2: 0
Sum 3: 0

输出:

Sum 2: 94646670
Sum 3: 0 

4 个答案:

答案 0 :(得分:2)

每当您运行并行任务时,两个任务都可能同时访问同一个内存(假设您没有可以序列化任务的SynchronizationContext,或者lock来访问门禁)。在这种情况下,一个线程在列表上迭代,而另一个线程在该列表中的元素周围移动。所以总和可能不止一次包含一些元素,而其他元素根本不包含。在某些情况下(不是在这个例子中),线程甚至可能读取元素的 part ,例如如果元素的大小超过原子读取的宽度(在Wintel上对齐32位)。

要证明这是原因,请尝试创建第二个列表并将其提供给第二个任务,如下所示:

var num1 = Enumerable.Range(-10000, 20001).Reverse().ToList();
var num2 = Enumerable.Range(-10000, 20001).Reverse().ToList();

Action task1 = () => 
{
    Console.WriteLine("Sum 2: {0}", num1.Sum());
};

Action task2 = () =>
{
    num2.Sort();
    Console.WriteLine("Sum 3: {0}", num2.Sum());
};

Parallel.Invoke(task1, task2);

如果您这样做,您将始终获得0两个答案。

答案 1 :(得分:1)

List<T>不是线程安全的,因此Parallel.Invoke发生在不同的线程上。你的答案是非确定性的,因为线程调用num.Sort()正在修改列表而另一个正在枚举它。

答案 2 :(得分:0)

SortSum不是原子的...一个线程正在计算总和,而sort正在移动数字,因此Sum将计算两次数字,并且一些数字将被完全跳过。您会注意到每次运行代码时实际上都会得到不同的结果。

答案 3 :(得分:0)

排序变异状态,因此使用OrderBy(x => x)而不是Sort(),您将获得可预测的排序结果。