为什么Task对象不会使用传递给它的参数?

时间:2013-11-19 13:08:59

标签: c# task

我正在使用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);
    }
}

3 个答案:

答案 0 :(得分:4)

你是closure的受害者。解决此问题的最简单方法是:

    for (int i = 0; i < 100; i++)
    {
        int v = i;
        t[i] = Task.Factory.StartNew(() => print(v));
    }

您可以找到更详细的解释herehere

答案 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 是闭包实例,然后每次迭代都会变化。)