You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 14:08:12