线程Lambda方法中传递循环索引仅打印最后值的问题
解决Lambda线程中循环索引只打印最后值的问题
嘿,这问题我太熟了——这是C#闭包捕获变量时的经典陷阱!咱们一步步来拆解原因和解决办法。
问题原因
你遇到的核心问题是:当在循环里用Lambda表达式创建线程时,Lambda捕获的是循环变量的引用,而不是当前迭代的具体值。
因为线程启动需要一定时间,等线程真正执行Lambda里的代码时,循环大概率已经跑完了,此时循环变量已经变成了最后一个迭代的值(比如你的ThreadNum=3,最后i会变成3),所以所有线程都会读取这个最终值,导致打印结果全是最后一个索引。
两种解决方案
方案1:在循环内创建局部变量保存当前索引
每次迭代时,把当前循环变量的值赋值给一个局部变量,让Lambda捕获这个局部变量。因为每个迭代的局部变量都是独立的实例,不会被后续循环覆盖,这样每个线程拿到的都是对应迭代的正确值。
修正后的代码示例:
class Program { public static EventWaitHandle handle = new EventWaitHandle(false, EventResetMode.AutoReset); public static EventWaitHandle autohandle = new EventWaitHandle(false, EventResetMode.AutoReset); static readonly int ThreadNum = 3; static void Main(string[] args) { object lk = new object(); for (int i = 0; i < ThreadNum; i++) { // 关键:创建当前迭代的局部变量,保存i的当前值 int currentIndex = i; new Thread(() => { handle.WaitOne(); Console.WriteLine($"当前索引:{currentIndex}"); autohandle.Set(); }).Start(); } // 唤醒线程并等待执行完成 for (int j = 0; j < ThreadNum; j++) { handle.Set(); autohandle.WaitOne(); } } }
方案2:使用参数化的ThreadStart
把索引作为参数直接传递给线程方法,完全避免闭包捕获的问题。线程启动时会拿到参数的副本,自然就是当前迭代的正确值。
修正后的代码示例:
class Program { public static EventWaitHandle handle = new EventWaitHandle(false, EventResetMode.AutoReset); public static EventWaitHandle autohandle = new EventWaitHandle(false, EventResetMode.AutoReset); static readonly int ThreadNum = 3; static void Main(string[] args) { object lk = new object(); for (int i = 0; i < ThreadNum; i++) { // 把当前索引作为参数传递给线程方法 new Thread(PrintIndex).Start(i); } // 唤醒线程并等待执行完成 for (int j = 0; j < ThreadNum; j++) { handle.Set(); autohandle.WaitOne(); } } // 独立的线程方法,接收索引参数 static void PrintIndex(object indexObj) { int index = (int)indexObj; handle.WaitOne(); Console.WriteLine($"当前索引:{index}"); autohandle.Set(); } }
额外提醒
如果是使用C# 5.0及以后的版本,foreach循环已经做了优化——每次迭代的变量都是独立的,不会出现这个问题,但for循环仍然存在闭包捕获引用的陷阱,所以要注意区分两种循环的差异。
内容的提问来源于stack exchange,提问作者Bercovici Adrian
相关产品推荐
相关产品推荐

