我正在使用Task
并行处理多个请求并将不同的参数传递给每个任务,但似乎所有任务都需要一个最终参数并使用该参数执行该方法。
以下是示例代码。我期待输出为:
0 1 2 3 4 5 6 ..99
但我明白了:
100 100 100 ..10。
可能在调用print方法之前,i
的值已经是100
但是每个方法都不应该打印传递给它的参数吗?为什么print方法的最终值为i
?
class Program
{
static void Main(string[] args)
{
Task[]t = new Task[100];
for (int i = 0; i < 100; i++)
{
t[i] = Task.Factory.StartNew(() => print(i));
}
Task.WaitAll(t);
Console.WriteLine("complete");
Console.ReadLine();
}
private static void print(object i)
{
Console.WriteLine((int)i);
}
}
答案 0 :(得分:4)
你是closure的受害者。解决此问题的最简单方法是:
for (int i = 0; i < 100; i++)
{
int v = i;
t[i] = Task.Factory.StartNew(() => print(v));
}
答案 1 :(得分:1)
在不考虑引用变量时会出现问题 它的范围。
Task[]t = new Task[100];
for (int i = 0; i < 100; i++)
{
t[i] = Task.Factory.StartNew(() => print(i));
}
Task.WaitAll(t);
您可能会认为,您的任务将考虑其执行中的每个第i个值。但这不会发生,因为任务执行将来某个时候开始。这意味着,变量i由for循环的步骤创建的所有闭包共享。到任务开始时,单个共享变量i的值。这就是为什么所有任务都打印相同的值。
解决方案是引入一个额外的临时变量 适当的范围。
Task[]t = new Task[100];
for (int i = 0; i < 100; i++)
{
var temp=i;
t[i] = Task.Factory.StartNew(() => print(temp));
}
Task.WaitAll(t);
此版本以任意顺序打印数字1,2,3,4..100,但每个都打印 号码将被打印。原因是声明了变量tmp 在for循环体的块范围内。这导致一个新的 名为tmp的变量将在for的每次迭代时实例化 环。 (相比之下,for循环的所有迭代共享一个实例 变量i。)
答案 2 :(得分:1)
有关信息,此处的另一个修复是使用Task
API的状态参数,即
t[i] = Task.Factory.StartNew(state => print((int)state), i);
不幸的是,由于状态参数为object
,这仍然是框的值,但它避免了每次调用需要一个完整的闭包和单独的委托(与上面显示的代码,编译器足够智能,可以为所有迭代使用单个委托实例;如果添加局部变量(如BartoszKP的答案中的v
),这是不可能的,因为 target 是闭包实例,然后每次迭代都会变化。)