2017-01-12 27 views
5

我有以下 -使用延迟任务的内部匿名方法在一个循环内

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

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

问题是,我的价值传递到ProcessResult似乎是值在启动时,而不是价值创建时的迭代。

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

+0

为什么使用'ContinueWith'而不是等待?至于'i',你的lambda捕获*变量*而不是变量的值。当实际读取它时,读取'i'将返回包含'i''的任何内容 - 实际执行对'ProcessResult(i,..)'的调用时。这是预期的行为。使用'await'将解决这个问题,通过消除lambda *和*简化代码 –

+0

来链接任务。这些可以长时间运行,而等待会暂停当前线程。 – Hector

+0

不,它不会。 'await' *等待*,不会阻止。它相当于'ContinueWith',而不是'Wait'。它使链接更容易*,因为它不需要lambdas和捕获。 –

回答

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

您需要在循环内创建一个临时变量;在您当前的代码中,您正在捕获变量i,而不是值,这意味着当最后执行延续任务时,循环已完成并且iN-1

for (int i = ...) 
{ 
    var temp = i; 
    var task = anotherTask.ContinueWith(t => ProcessResult(temp, t.Resume)); 
} 
+1

或使用'await'而不是'ContinueWith' –

+1

@PanagiotisKanavos是的,但问题是关于为什么代码的行为方式,而不是如果这是完成这项工作的最佳方式。 – InBetween

+0

谢谢!我认为编译器/垃圾收集器可以处理循环继续,变量不会被丢弃? – Hector

2

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

你可以通过在循环中使用一个临时变量来解决这个问题。您的代码将是更清洁了很多但如果你使用async/await代替ContinueWith和lambda表达式,如:

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

在一般情况下,你可以通过复制循环变量进入循环的范围内定义的变量避免捕获的问题。

这解决了问题,因为临时变量只存在于循环体内。拉姆达是循环体内创建,捕捉局部的,不变的变量:

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); 
    //... 
}