44

我有以下代码,在类型之间转换项目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 表达式可能返回voidTaskTask<T>,其中任何一个都不能转换为 'System.Func<R,int,L>'。”

我相信我遗漏了关于 async/await 关键字的一些内容,但我不知道是什么。有谁知道我可以如何修改我的代码以使其工作?

4

1 回答 1

77

你可以通过考虑游戏中的类型来解决这个问题。例如,MapToLocalObject- 当被视为异步函数时 - 确实映射 fromRL. 但是如果你把它看成一个同步函数,它会从 映射RTask<L>

Task是一个“未来”,因此Task<L>可以被认为是在未来某个时间点产生的类型。L

所以你可以很容易地从一个序列转换为R一个序列Task<L>

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

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

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

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

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

Task<L>[] orderedMappingTasks = mappingTasks.OrderByCompletion();
foreach (var task in orderedMappingTasks)
{
  var mappedItem = await task;
  ...
}
于 2013-02-18T14:59:50.927 回答