For循环中创建带参数Thread时迭代变量j值异常原因排查
循环内启动线程时迭代变量值异常的原因
我在测试一段代码,需要在for循环中重复执行任务,每次循环更新变量。代码如下:
for (int j = 0; j < 6; j++) { _waitNextBatch++; Console.WriteLine($"{_waitNextBatch}"); BatchSensor bs = new BatchSensor { SensorName = $"batch - {j}" }; new Thread(() => SimulateBS(bs, j)).Start(); }
运行后发现,SimulateBS方法能获取正确的bs对象,但所有线程的迭代变量j要么是循环最后一次的值6,要么是混乱的中间值,比如输出:
batch - 0 | 6 batch - 2 | 6 batch - 5 | 6 batch - 4 | 6 batch - 3 | 6 batch - 1 | 6
或者:
batch - 0 | 2 batch - 1 | 3 batch - 2 | 3 batch - 3 | 5 batch - 4 | 5 batch - 5 | 6
核心原因:闭包捕获的是变量引用,而非当前值
- 代码中的lambda表达式
() => SimulateBS(bs, j)捕获的是迭代变量j的引用,而非创建线程那一刻j的具体数值。整个for循环里,j是同一个变量,每次循环只是更新它的取值。 - for循环的执行速度远快于线程的启动和初始化,当线程真正开始执行
SimulateBS时,循环可能已经完成了多次迭代,甚至直接走到了循环结束后的j=6(因为循环条件是j<6,最后一次循环后j会自增到6)。 - 这就导致线程读取到的
j是它执行瞬间变量的当前值,而非创建线程时的数值,所以会出现要么全是6,要么是混乱中间值的情况。
为什么bs能获取正确值?
因为每次循环内部都新建了BatchSensor bs = new BatchSensor { ... },每个线程捕获的是当前循环中新建的bs对象的引用。每个循环的bs都是独立实例,不会被后续循环的赋值覆盖,所以每个线程能拿到对应循环的正确对象。
解决方法
要让线程拿到创建时的j值,核心是让每个线程捕获当前迭代的值的副本而非变量引用,常见两种方式:
- 在循环内部创建局部变量保存当前
j的值,让lambda捕获这个局部变量:
for (int j = 0; j < 6; j++) { _waitNextBatch++; Console.WriteLine($"{_waitNextBatch}"); BatchSensor bs = new BatchSensor { SensorName = $"batch - {j}" }; // 保存当前迭代的j值到局部变量 int currentJ = j; new Thread(() => SimulateBS(bs, currentJ)).Start(); }
- 如果使用C# 5及以上版本,也可以改用
Parallel.For等并行循环方式,但第一种方法兼容性更好,适用于所有C#版本。
内容的提问来源于stack exchange,提问作者Oiproks
相关产品推荐
相关产品推荐

