在IEnumerable.Select中调用异步方法

PKe*_*eno 36 c# async-await

我有以下代码,在类型之间转换项目RL使用异步方法:

class MyClass<R,L> {

    public async Task<bool> MyMethodAsync(List<R> remoteItems) {
        ...

        List<L> mappedItems = new List<L>();
        foreach (var remoteItem  in remoteItems )
        {
            mappedItems.Add(await MapToLocalObject(remoteItem));
        }

        //Do stuff with mapped items

        ...
    }

    private async Task<L> MapToLocalObject(R remoteObject);
}
Run Code Online (Sandbox Code Playgroud)

这是否可以使用IEnumerable.Select调用(或类似)来减少代码行?我试过这个:

class MyClass<R,L> {

    public async Task<bool> MyMethodAsync(List<R> remoteItems) {
        ...

        List<L> mappedItems = remoteItems.Select<R, L>(async r => await MapToLocalObject(r)).ToList<L>();

        //Do stuff with mapped items

        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

但我得到错误:

"无法将异步lambda表达式转换为委托类型 'System.Func<R,int,L>'.异步lambda表达式可能会返回void, Task或者Task<T>,没有一个可以转换为 'System.Func<R,int,L>'."

我相信我错过了关于async/await关键字的一些内容,但我无法弄清楚是什么.有没有人知道如何修改我的代码以使其有效?

Ste*_*ary 68

您可以通过考虑游戏中的类型来解决这个问题.例如,MapToLocalObject- 当被视为异步函数时 - 映射RL.但是如果你将它视为同步函数,它会映射RTask<L>.

Task是一个"未来",所以Task<L>可以作为一种类型被认为是产生一个L在将来某一时刻.

因此,您可以轻松地从序列转换R为以下序列Task<L>:

IEnumerable<Task<L>> mappingTasks = remoteItems.Select(remoteItem => MapToLocalObject(remoteItem));
Run Code Online (Sandbox Code Playgroud)

请注意,这与您的原始代码之间存在重要的语义差异.原始代码在继续下一个对象之前等待映射每个对象; 此代码将同时启动所有映射.

您的结果是一系列任务 - 一系列未来L结果.要处理任务序列,有一些常见的操作.Task.WhenAll并且Task.WhenAny是最常见要求的内置操作.如果要等到所有映射都完成,您可以执行以下操作:

L[] mappedItems = await Task.WhenAll(mappingTasks);
Run Code Online (Sandbox Code Playgroud)

如果您希望在完成后处理每个项目,可以OrderByCompletion我的AsyncEx库中使用:

Task<L>[] orderedMappingTasks = mappingTasks.OrderByCompletion();
foreach (var task in orderedMappingTasks)
{
  var mappedItem = await task;
  ...
}
Run Code Online (Sandbox Code Playgroud)

  • 如果将任务序列归类为一个集合,则可以访问这些任务的结果,例如,在取消的情况下生成部分结果。 (2认同)