我正在尝试同时执行多项任务,并且遇到了一个我似乎无法理解或解决的问题。
我曾经有过这样的功能:
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;
}
我想使用for
在Task.Run()
循环中调用。但是我找不到将参数发送到此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中使用局部变量会“捕获”它们的值但看起来却没有;它将始终采用索引的值,就好像在for
循环结束时捕获该值一样。 index
变量在每次迭代时在lambda中以400计算,所以当然我得到IndexOutOfRangeException
400次(items.Count
实际上是MAX
)。
我真的不确定这里发生了什么(尽管我对此非常好奇)并且我不知道如何做我想要实现的目标。欢迎任何提示!
答案 0 :(得分:6)
制作索引变量的本地副本:
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);
的一种方式:
{{1}}
答案 1 :(得分:1)
所有lambdas都捕获相同的变量,这是你的循环变量。但是,只有在循环结束后才会执行所有lambda。在那个时间点,循环变量具有最大值,因此你的所有lambda都使用它。
斯蒂芬·克利里在答案中表明如何解决这个问题。Eric Lippert写了一篇关于此的detailled two-part series。