在IEnumerable.Select中调用异步方法

时间:2013-02-18 14:20:56

标签: 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);
}

这是否可以使用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

        ...
    }
}

但我得到错误:

  

“无法将异步lambda表达式转换为委托类型   'System.Func<R,int,L>'。异步lambda表达式可能返回void,   TaskTask<T>,其中任何一个都无法兑换为   'System.Func<R,int,L>'“。

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

1 个答案:

答案 0 :(得分:64)

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

Task是一个“未来”,因此可以将Task<L>视为将来生成L的类型。

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

IEnumerable<Task<L>> mappingTasks = remoteItems.Select(remoteItem => MapToLocalObject(remoteItem));

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

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

L[] mappedItems = await Task.WhenAll(mappingTasks);

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

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