我有一个简单的同步方法,如下所示:

public IEnumerable<Foo> MyMethod(Source src)
{
    // returns a List of Oof objects from a web service
    var oofs = src.LoadOofsAsync().Result;
    foreach(var oof in oofs)
    {
         // transforms an Oof object to a Foo object
         yield return Transform(oof);
    }
}

由于该方法是Web应用程序的一部分,因此最好尽可能有效地使用所有资源。因此,我想将该方法更改为异步方法。最简单的选择是执行以下操作:
public async Task<IEnumerable<Foo>> MyMethodAsync(Source src)
{
    var oofs = await src.LoadOofsAsync();
    return oofs.Select(oof => Transform(oof));
}

我不是async/awaitIEnumerable的专家。但是,据我了解,使用这种方法“杀死”了IEnumerable的好处,因为等待Task直到加载整个集合,从而忽略了IEnumerable集合的“懒惰”。

在其他StackOverflow帖子中,我阅读了一些有关使用Rx.NET(或System.Reactive)的建议。快速浏览文档,我已经了解到IObservable<T>IEnumerable<T>的异步替代方案。但是,使用幼稚的方法并尝试键入以下命令是行不通的:
public async IObservable<Foo> MyMethodReactive(Source src)
{
    var oofs = await src.LoadOofsAsync();
    foreach(var oof in oofs)
    {
        yield return Transform(oof);
    }
}

我遇到了一个编译错误,即IObservable<T>既未实现GetEnumerator()也未实现GetAwaiter()-因此无法同时使用yieldasync。我没有更深入地阅读Rx.NET的文档,因此我可能只是错误地使用了该库。但是我不想花时间学习一个新的框架来修改单个方法。

使用新的possibilities in C# 7,现在可以实现自定义类型。因此,从理论上讲,我可以实现IAsyncEnumerable,它将定义GetEnumerator()GetAwaiter()方法。但是,根据我以前的经验,我记得创建GetEnumerator()的自定义实现的一次失败尝试……我最终得到了一个隐藏在容器中的简单List。

因此,我们有4种可能的方法来解决任务:
  • 保持代码同步,但使用IEnumerable
  • 将其更改为异步,但将IEnumerable包装在Task<T>
  • 学习和使用Rx.NET(System.Reactive)
  • 创建具有C#7功能的自定义IAsyncEnumerable

  • 这些尝试中的每一种都有哪些优点和缺点?其中哪一个对资源利用影响最大?

    最佳答案



    在您的情况下,听起来最好的选择是Task<IEnumerable<T>>。这是每个选项都擅长的地方:

  • 没有I/O,但占用大量CPU时,同步代码(或并行同步代码)比较出色。如果您有I/O代码同步等待(例如您的第一个方法实现),则CPU在等待Web服务响应而无所事事时,只是在燃烧周期。
  • Task<IEnumerable<T>>用于当有I/O操作来获取集合时。等待I/O操作的线程可以在等待时在其上安排其他内容。
    这听起来像您的情况。
  • Rx最适合于推送方案:在“响应”到您的代码的数据被“推送”的情况下。常见的示例是接收股票市场价格数据的应用程序或聊天应用程序。
  • IAsyncEnumerable用于当您有一个集合时,其中每个项目都将需要或生成一个异步任务。一个例子:遍历一组项目并对每个项目执行某种独特的数据库查询。如果您的Transform实际上是与I/O绑定(bind)的异步方法,那么这可能更明智。
  • 关于c# - 异步返回集合的各种方式(具有C#7功能),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45921385/

    10-09 06:57
    查看更多