我试图同时运行几个任务,我遇到了一个似乎无法理解或解决的问题 .
我以前有这样的功能:
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 回答
你的所有lambda都捕获相同的变量,这是你的循环变量 . 但是,只有在循环结束后才会执行所有lambda . 在那个时间点,循环变量具有最大值,因此所有lambda都使用它 .
Stephen Cleary在他的回答中表明如何解决它 .
Eric Lippert写了一篇关于这个的文章 .
制作索引变量的本地副本:
这是由于C#执行
for
循环的方式:只有一个index
变量被更新,并且所有lambda都捕获相同的变量(使用lambdas,捕获变量,而不是值) .作为旁注,我建议你:
避免
async void
. 您永远不会知道async void
方法何时完成,并且它们具有困难的错误处理语义 .await
所有异步操作 . 即,不要忽略从Task.Run
返回的任务 . 对他们使用Task.WhenAll
或类似的await
. 这允许异常传播 .例如,这是使用
WhenAll
的一种方法: