【问题标题】:Calling async method in IEnumerable.Select在 IEnumerable.Select 中调用异步方法
【发布时间】:2023-03-14 00:48:01
【问题描述】:

我有以下代码,使用异步方法在 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&lt;R,int,L&gt;'。异步 lambda 表达式可能返回 voidTaskTask&lt;T&gt;,它们都不能转换为 'System.Func&lt;R,int,L&gt;'。”

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

【问题讨论】:

  • 应该可以,不指定类型参数试试?
  • @ofstream:不,它不应该工作。错误消息对此非常具体。
  • 如果我不指定类型参数 (remoteItems.Select(async r =&gt; await MapToLocalObject(r)).ToList()),我会得到一个 List&lt;Task&lt;L&gt;&gt;&gt;,这不是我想要的。
  • 啊,我们走了。您必须返回任务,否则它不是异步方法。您需要进行两次选择,一次选择映射,然后选择 task.result,然后选择 tolist。那应该会给你一个 List.
  • @AlexFilipovici 这只会对任务进行阻塞等待,使方法不异步。

标签: c# async-await


【解决方案1】:

您可以通过考虑游戏中的类型来解决这个问题。例如,MapToLocalObject - 当被视为异步函数时 - 确实从 R 映射到 L。但是如果你把它看成一个同步函数,它会从R映射到Task&lt;L&gt;

Task 是一个“未来”,因此 Task&lt;L&gt; 可以被认为是一种类型, 在未来的某个时间点会产生一个L

因此您可以轻松地将R 序列转换为Task&lt;L&gt; 序列:

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

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

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

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

如果您希望在完成每个项目时对其进行处理,您可以使用来自my AsyncEx libraryOrderByCompletion

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

【讨论】:

  • 我很想知道在 remoteItems.Select(remoteItem => MapToLocalObject(remoteItem)) 的末尾添加“.ToArray()”会产生什么效果。如果外部任务被取消,它不会让我访问已完成任务的结果吗?抱歉,如果不清楚!
  • 如果您将任务序列具体化为一个集合,您将能够访问这些任务的结果,例如,在取消的情况下构建部分结果。
猜你喜欢
  • 1970-01-01
  • 2015-11-09
  • 1970-01-01
  • 2015-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多