【问题标题】:Using anonymous methods inside of a delayed task inside of a loop在循环内的延迟任务中使用匿名方法
【发布时间】:2017-01-12 08:18:15
【问题描述】:

我有以下 -

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

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

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

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

【问题讨论】:

  • 你为什么用ContinueWith而不是await?至于i,您的 lambda 捕获 variable 而不是变量的值。读取i 将返回您实际读取它时包含的任何i - 当实际执行对ProcessResult(i,..) 的调用时。顺便说一下,这是预期的行为。使用 await 将通过摆脱 lambda 来解决此问题简化您的代码
  • 链接任务。这些可以长时间运行,而 await 会暂停当前线程。
  • 不,不会的。 await 等待,它不会阻塞。它相当于ContinueWith,而不是Wait。它使链接 很多 变得更容易*,因为它不需要 lambda 和捕获。
  • 无论如何,您可以将状态参数传递给ContinueWith 并在您的lambda 中访问它。这被 await 取代,并在 .NET 4.5 之后直接访问变量
  • 谢谢。我是任务/等待的新手。我想我需要再读一遍。

标签: c# lambda async-await task


【解决方案1】:

您需要将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。 p>

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

    【讨论】:

    • 或者用await代替ContinueWith
    • @PanagiotisKanavos 是的,但问题是为什么代码的行为方式如此,而不是这是否是完成这项工作的最佳方式。
    • 谢谢!我认为编译器/垃圾收集器可以处理循环继续,变量不会被释放?
    • @user2036256 首先,变量不能被释放,因为它不是一次性对象。其次,为什么它与您当前的代码有什么不同?您似乎将变量的 scopelifetime 混为一谈。它们肯定是不一样的。你的循环变量i 也超出了范围,它似乎并没有破坏你的代码。为什么temp 会有所不同?
    【解决方案3】:

    使用外部变量的 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);
        //...
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-21
      • 2014-02-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-01
      • 1970-01-01
      • 2013-01-25
      • 2015-08-21
      相关资源
      最近更新 更多