异步lambda中的参数
本文关键字:参数 lambda 异步 | 更新日期: 2023-09-27 18:21:45
我试图同时运行多个任务,但遇到了一个似乎无法理解或解决的问题。
我以前有一个这样的功能:
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中使用局部变量会"捕获"它们的值,但看起来并没有;它将总是取index的值,就好像该值将在CCD_。在每次迭代中,index
变量在lambda中的值为400,所以我当然得到了400次IndexOutOfRangeException
(items.Count
实际上是MAX
)。
我真的不确定这里发生了什么(尽管我真的很好奇),我也不知道如何实现我想要实现的目标。欢迎任何提示!
制作索引变量的本地副本:
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,捕获变量,而不是值)。
顺便说一句,我建议你:
- 避免
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);
所有lambdas都捕获相同的变量,该变量是循环变量。但是,只有在循环完成之后,才会执行所有的lambda。在这个时间点上,循环变量具有最大值,因此所有Lambda都使用它
Stephen Cleary在他的回答中展示了如何修复它。
埃里克·利珀特写了一个由两部分组成的详细系列。