异步Lambda中的参数

7

我正在尝试同时运行多个任务,但遇到了一个问题,我似乎无法理解或解决。

我曾经有这样一个函数:

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>,每个人都建议在类似情况下使用lambda表达式:
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;
    }
}

我曾认为在lambda中使用局部变量会"捕获"它们的值,但事实并非如此;它将始终采用索引的值,就好像该值将在for循环结束时被捕获一样。在每次迭代中,index变量在lambda中被评估为400,因此我会得到400次IndexOutOfRangeExceptionitems.Count实际上是MAX)。
我真的不确定这里发生了什么(虽然我真的很好奇),也不知道如何实现我想要的东西。任何提示都欢迎!
2个回答

7

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

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都捕获同一个变量(使用lambda时,捕获的是变量而不是值)。
顺便提一句,我建议你:
  1. 避免使用async void。你永远不知道async void方法何时完成,且它们有困难的错误处理语义。
  2. await所有异步操作。即不要忽略从Task.Run返回的任务。使用Task.WhenAll或类似方法来等待它们。这样可以使异常传播。
例如,下面是一种使用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);

2

所有的lambda都捕获了同一个变量,即循环变量。但是,在循环完成后,所有的lambda才被执行。此时,循环变量具有最大值,因此所有的lambda都使用它。

Stephen Cleary在他的回答中展示了如何修复它。

Eric Lippert写了一篇详细的两部分系列文章,关于这个问题:Closing Over the Loop Variable Considered Harmful (Part One)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接