for循环生成后台线程时的异常行为问题咨询
闭包捕获循环变量导致的线程行为异常
先看你给出的测试代码:
Thread thread; for (int i = 6000; i < 6006; i++) { //Thread.Sleep(1); thread = new Thread(() => derp(i)); thread.IsBackground = true; thread.Start(); } private void derp(int a) { MessageBox.Show(a.ToString()); }
为什么不加Thread.Sleep(1)会行为异常?
这是C#里闭包捕获循环变量的经典陷阱:你的lambda表达式() => derp(i)捕获的是循环变量i的引用,而不是每次循环时i的具体值。
当循环快速执行时,线程的启动速度远远赶不上循环的迭代速度——循环已经把i从6000递增到6006了,那些线程才陆续开始执行。此时所有线程访问的都是同一个i变量的最终值6006,所以你会看到弹出的都是6006的消息框,完全不符合预期。
为什么加了Thread.Sleep(1)就正常了?
加了Sleep之后,每次循环都会暂停1毫秒,给当前创建的线程足够的时间启动并执行derp(i)。这时候线程在i递增到下一个值之前,就已经捕获到了当前循环迭代的i值,所以能正确显示6000到6005。
不过这种解决方法是依赖时间的临时hack,非常不可靠——如果系统负载高、线程启动延迟,问题还是会复现。
正确的解决方案:捕获临时变量
在循环内部创建一个临时变量,把当前i的值赋值给它,让lambda捕获这个临时变量,这样每个线程都会拿到独立的、对应迭代的值:
Thread thread; for (int i = 6000; i < 6006; i++) { int temp = i; // 创建临时变量保存当前i的值 thread = new Thread(() => derp(temp)); thread.IsBackground = true; thread.Start(); } private void derp(int a) { MessageBox.Show(a.ToString()); }
如果你使用的是C# 5及以上版本,也可以改用foreach循环(C# 5对foreach的变量捕获做了优化,每次迭代都会创建新的变量),不过for循环没有这个优化,还是需要通过临时变量来解决。
内容的提问来源于stack exchange,提问作者Ayy
相关产品推荐
相关产品推荐

