C# WinForm中TPL Task调用出现意外结果:我哪里操作出错了?
问题原因与修复方案
嘿William,这个问题我太熟悉了——这是C#里经典的闭包捕获变量陷阱!
为什么会出现大量Testing30?
你在for循环里创建Task时用了lambda表达式() => DoWork(new object[] {i}),这里的lambda捕获的是变量i本身,而不是当前迭代时i的具体数值。
因为for循环的执行速度远快于Task的启动和执行,当大部分Task真正开始执行lambda的时候,循环已经跑完了,此时i的值已经变成了30(循环结束条件是i < 30,最后一次循环后i会自增到30),所以这些Task就会把30传入DoWork,导致你看到大量的Testing30。
修复方案
有几种简单的方法可以解决这个问题:
方法1:在循环内创建局部变量保存当前i的值
每次迭代时把i的数值复制到一个局部变量中,让lambda捕获这个局部变量(每个迭代的局部变量都是独立的):
private void btnTest_Click(object sender, EventArgs e) { for (int i = 0; i < 30; i++) { int currentI = i; // 关键:用局部变量保存当前迭代的i值 Task t1 = new Task(() => DoWork(new object[] {currentI}) ); t1.Start(); } }
方法2:使用Task.Run的重载直接传递参数
可以跳过闭包,直接把i作为Task的状态参数传递,这样就不会有捕获变量的问题了:
// 先简化DoWork方法,不需要数组直接转int private void DoWork(object state) { int i = (int)state; Invoke(new MethodInvoker(delegate() { richTextBox1.Text += "Testing" + i.ToString() + "\n"; })); } private void btnTest_Click(object sender, EventArgs e) { for (int i = 0; i < 30; i++) { // 直接把i作为state参数传递给Task Task.Run(DoWork, i); } }
额外小提示
如果是C# 5及以上版本,foreach循环已经默认修复了这个捕获变量的问题,但for循环依然会存在这个陷阱,所以以后写for循环结合lambda/Task的时候一定要注意哦!
这样修改后,你应该就能看到0到29的随机序号正常输出啦😉
内容的提问来源于stack exchange,提问作者Williams
相关产品推荐
相关产品推荐

