为何使用Tasks时Linq Sum()会输出不同结果?
嘿,这个问题我之前也踩过坑,大概率是你在多任务里共享了一个可变的累加变量,而且没做线程同步导致的竞态条件!
问题核心原因分析
这种情况最常见的场景是:你没有让每个Task独立计算Enumerable.Range的Sum结果,而是把所有Task的累加操作都写到同一个共享变量里,并且没做任何线程安全保障。
举个典型的错误代码例子:
var numbers = Enumerable.Range(1, 1000); int total = 0; // 多个Task共享的可变变量 var tasks = new List<Task>(); // 启动多个任务同时累加 for (int i = 0; i < 4; i++) { tasks.Add(Task.Run(() => { foreach (var num in numbers) { total += num; // 这里的+=不是原子操作! } })); } Task.WaitAll(tasks.ToArray()); Console.WriteLine(total); // 每次运行结果都可能不一样,远小于预期值
为什么结果会不稳定?因为total += num这个操作本质上是三步:
- 读取
total的当前值 - 加上
num得到新值 - 将新值写回
total
当多个线程同时执行这三步时,就会出现竞态冲突:比如线程A刚读取了total=100,还没来得及写回新值,线程B也读取了total=100,两个线程各自计算后写回,最终total的值会被其中一个线程的结果覆盖,导致部分累加值丢失。
而如果是每个Task独立调用numbers.Sum()再汇总结果,完全不会有问题——因为Linq的Sum()方法在执行时,会创建自己的局部累加变量,每个Task的Sum操作完全独立,不会互相干扰:
var numbers = Enumerable.Range(1, 1000); var tasks = new List<Task<int>>(); for (int i = 0; i < 4; i++) { tasks.Add(Task.Run(() => numbers.Sum())); // 每个任务独立计算Sum } var results = Task.WhenAll(tasks).Result; int total = results.Sum(); // 汇总独立的结果 Console.WriteLine(total); // 结果稳定符合预期
解决办法
如果确实需要在多线程中共享累加变量,你需要用线程安全的方式操作:
- 使用
Interlocked.Add方法,它是原子操作,能保证读取、修改、写回的过程不会被其他线程打断:
var numbers = Enumerable.Range(1, 1000); int total = 0; var tasks = new List<Task>(); for (int i = 0; i < 4; i++) { tasks.Add(Task.Run(() => { foreach (var num in numbers) { Interlocked.Add(ref total, num); // 原子累加 } })); } Task.WaitAll(tasks.ToArray()); Console.WriteLine(total); // 结果正确稳定
- 或者使用
lock语句,锁定共享变量的操作区域,同一时间只有一个线程能执行累加:
var numbers = Enumerable.Range(1, 1000); int total = 0; object lockObj = new object(); // 用于锁定的对象 var tasks = new List<Task>(); for (int i = 0; i < 4; i++) { tasks.Add(Task.Run(() => { foreach (var num in numbers) { lock(lockObj) { total += num; } } })); } Task.WaitAll(tasks.ToArray()); Console.WriteLine(total); // 结果正确稳定
内容的提问来源于stack exchange,提问作者Khawaja Asim
相关产品推荐
相关产品推荐

