在IEnumerable.Select中调用异步方法
我有以下代码,使用异步方法在类型R
和L
之间转换项目:
class MyClass { public async Task MyMethodAsync(List remoteItems) { ... List mappedItems = new List(); foreach (var remoteItem in remoteItems ) { mappedItems.Add(await MapToLocalObject(remoteItem)); } //Do stuff with mapped items ... } private async Task MapToLocalObject(R remoteObject); }
这是否可以使用IEnumerable.Select调用(或类似)来减少代码行? 我试过这个:
class MyClass { public async Task MyMethodAsync(List remoteItems) { ... List mappedItems = remoteItems.Select(async r => await MapToLocalObject(r)).ToList(); //Do stuff with mapped items ... } }
但我得到错误:
“无法将异步lambda表达式转换为委托类型
'System.Func'
。异步lambda表达式可能返回void
,Task
或Task
,其中任何一个都不能转换为'System.Func'
。“
我相信我错过了关于async / await关键字的一些内容,但我无法弄清楚是什么。 有没有人知道如何修改我的代码以使其工作?
您可以通过考虑游戏中的类型来解决这个问题。 例如, MapToLocalObject
– 当被视为异步函数时 – 从R
映射到L
但是如果将它视为同步函数,它会从R
映射到Task
。
Task
是一个“未来”,因此可以将Task
视为将来某个时刻产生L
的类型。
因此,您可以轻松地将R
序列转换为Task
序列:
IEnumerable> mappingTasks = remoteItems.Select(remoteItem => MapToLocalObject(remoteItem));
请注意,此代码与原始代码之间存在重要的语义差异。 您的原始代码在继续下一个对象之前等待映射每个对象; 此代码将同时启动所有映射。
您的结果是一系列任务 – 一系列未来的L
结果。 要处理任务序列,有一些常见的操作。 Task.WhenAll
和Task.WhenAny
是最常见需求的内置操作。 如果要等到所有映射都完成,您可以执行以下操作:
L[] mappedItems = await Task.WhenAll(mappingTasks);
如果您希望在完成时处理每个项目,可以使用我的AsyncEx库中的OrderByCompletion :
Task[] orderedMappingTasks = mappingTasks.OrderByCompletion(); foreach (var task in orderedMappingTasks) { var mappedItem = await task; ... }