在循环内部的延迟任务中使用匿名方法

时间:2017-01-12 08:18:16

标签: c# lambda async-await task

我有以下内容 -

for (int i = 0; i < N; ++i)
{
    var firstTask = DoSomething(...);

    var task = anotherTask.ContinueWith(
        (t) => ProcessResult(i, t.Result));
}

问题是传递给ProcessResult的i的值似乎是它启动时的值,而不是创建它时的迭代值。

防止这种情况的最佳方法是什么?

3 个答案:

答案 0 :(得分:2)

你需要在循环中创建一个临时变量;在当前代码中,您正在捕获变量i,而不是值,这意味着当最终执行延续任务时,循环已经完成,i为{{ 1}}。

N-1

答案 1 :(得分:2)

您需要将i的值捕获到自己的变量中。

for (int i = 0; i < N; ++i)
{
    var count = i;

    var firstTask = DoSomething(...);

    var task = anotherTask.ContinueWith(
        (t) => ProcessResult(count, t.Result));
}

示例:

for (int i = 0; i < 5; ++i)
{
    var a = i;
    var task = Task.Delay(0).ContinueWith((t) => a.Dump());
}

输出如下内容:

0
2
1
4
3

但是这个:

for (int i = 0; i < 5; ++i)
{
    var task = Task.Delay(0).ContinueWith((t) => i.Dump());
}

输出:

5
5
5
5
5

答案 2 :(得分:2)

使用外部变量的lambda实际上捕获变量,而不是存储在其中的值。这意味着随着循环的进行,您从捕获的变量中读取的值也会发生变化。

您可以通过在循环内使用临时变量来解决此问题。如果你使用async/await而不是ContinueWith和lambdas,你的代码会更清晰,例如:

for (int i=0;i<N;i++)
{
    //...
    var result=await thatOtherAsyncMethod(...);
    ProcessResult(i, result));
}

通常,您可以通过将循环变量复制到循环范围内定义的变量来避免捕获问题。

这解决了问题,因为临时变量仅存在于循环体内。 lambda 在循环体内创建,并捕获一个本地的,不变的变量:

for (int i=0;i<N;i++)
{
    var temp=i;
    var myLambda = new Action(()=>MyMethod(temp));

    //This runs with the local copy, not i
    myLambda();
}

更好的方法是避免捕获并将循环值作为状态参数传递给ContinueWith,例如:

for (int i = 0; i < N; ++i)
{
    //...
    var task = anotherTask.ContinueWith(
                               (t,state) => ProcessResult((int)state, t.Result),
                               i);
    //...
}