首页 文章

异步lambdas中的参数

提问于
浏览
6

我试图同时运行几个任务,我遇到了一个似乎无法理解或解决的问题 .

我以前有这样的功能:

private void async DoThings(int index, bool b) {
    await SomeAsynchronousTasks();
    var item = items[index];
    item.DoSomeProcessing();
    if(b)
        AVolatileList[index] = item; //volatile or not, it does not work
    else
        AnotherVolatileList[index] = item;
}

我想使用 Task.Run()for 循环中调用 . 但是我找不到将参数发送到 Action<int, bool> 的方法,并且每个人都建议在类似的情况下使用lambdas:

for(int index = 0; index < MAX; index++) { //let's say that MAX equals 400 
    bool b = CheckSomething();
    Task.Run(async () => {
        await SomeAsynchronousTasks();
        var item = items[index]; //here, index is always evaluated at 400
        item.DoSomeProcessing();
        if(b)
            AVolatileList[index] = item; //volatile or not, it does not work
        else
            AnotherVolatileList[index] = item;
    }
}

我认为在lambdas中使用局部变量会使它们的值变为"capture"但它看起来并非如此;它将始终采用索引的值,就好像在 for 循环结束时捕获该值一样 . index 变量在每次迭代时在lambda中以400计算,所以当然我得到 IndexOutOfRangeException 400次( items.Count 实际上是 MAX ) .

我真的不确定这里发生了什么(虽然我真的很好奇)但我不知道如何做我想要实现的目标 . 任何提示都是受欢迎的!

2 回答

  • 6

    你的所有lambda都捕获相同的变量,这是你的循环变量 . 但是,只有在循环结束后才会执行所有lambda . 在那个时间点,循环变量具有最大值,因此所有lambda都使用它 .

    Stephen Cleary在他的回答中表明如何解决它 .

    Eric Lippert写了一篇关于这个的文章 .

  • 1

    制作索引变量的本地副本:

    for(int index = 0; index < MAX; index++) {
      var localIndex = index;
      Task.Run(async () => {
        await SomeAsynchronousTasks();
        var item = items[index];
        item.DoSomeProcessing();
        if(b)
            AVolatileList[index] = item;
        else
            AnotherVolatileList[index] = item;
      }
    }
    

    这是由于C#执行 for 循环的方式:只有一个 index 变量被更新,并且所有lambda都捕获相同的变量(使用lambdas,捕获变量,而不是值) .

    作为旁注,我建议你:

    • 避免 async void . 您永远不会知道 async void 方法何时完成,并且它们具有困难的错误处理语义 .

    • await 所有异步操作 . 即,不要忽略从 Task.Run 返回的任务 . 对他们使用 Task.WhenAll 或类似的 await . 这允许异常传播 .

    例如,这是使用 WhenAll 的一种方法:

    var tasks = Enumerable.Range(0, MAX).Select(index =>
      Task.Run(async () => {
        await SomeAsynchronousTasks();
        var item = items[localIndex];
        item.DoSomeProcessing();
        if(b)
            AVolatileList[localIndex] = item;
        else
            AnotherVolatileList[localIndex] = item;
      }));
    await Task.WhenAll(tasks);
    

相关问题