异步返回集合的各种方法(使用C#7功能)

lss*_*lss 9 c# asynchronous system.reactive .net-core

我有一个简单的同步方法,看起来像这样:

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); 
    }
}
Run Code Online (Sandbox Code Playgroud)

由于该方法是Web应用程序的一部分,因此尽可能有效地使用所有资源是很好的.因此,我想将方法​​更改为异步方法.最简单的选择是做这样的事情:

public async Task<IEnumerable<Foo>> MyMethodAsync(Source src)
{
    var oofs = await src.LoadOofsAsync();
    return oofs.Select(oof => Transform(oof));
}
Run Code Online (Sandbox Code Playgroud)

我不是专家要么async/ awaitIEnumerable.但是,从我的理解,使用这种方法"杀死"的好处IEnumerable,因为等待任务,直到整个集合被加载,从而省略了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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

凭借C#7中的新功能,现在可以实现自定义类型.因此,理论上,我可以实现一个IAsyncEnumerable定义两者GetEnumerator()GetAwaiter()方法的方法.但是,根据我以前的经验,我记得创建自定义实现的尝试失败GetEnumerator()...我最终得到了一个隐藏在容器中的简单List.

因此,我们有4种可能的方法来解决任务:

  1. 保持代码同步,但有 IEnumerable
  2. 将其更改为异步,但包装IEnumerableTask<T>
  3. 学习和使用Rx.NET(System.Reactive)
  4. IAsyncEnumerable使用C#7功能创建自定义

每次尝试的好处和缺点是什么?哪些对资源利用影响最大?

Shl*_*omo 2

  • 保持代码同步,但使用 IEnumerable
  • 将其更改为异步,但将 IEnumerable 包装在任务中
  • 学习和使用 Rx.NET (System.Reactive)
  • 使用 C# 7 功能创建自定义 IAsyncEnumerable

这些尝试各自的优点和缺点是什么?其中哪一个对资源利用率影响最显着?

根据您的情况,听起来最好的选择是Task<IEnumerable<T>>。以下是每个选项的优点:

  1. 当没有 I/O 但 CPU 使用率很高时,同步代码(或并行同步代码)表现出色。如果您有同步等待的 I/O 代码(如您的第一个方法实现),CPU 只是在等待 Web 服务响应时消耗周期而不执行任何操作。

  2. Task<IEnumerable<T>>适用于有 I/O 操作来获取集合的情况。然后,运行等待 I/O 操作的线程可以在等待时安排其他事情。这听起来像你的情况。

  3. Rx 最适合推送场景:数据被“推送”到您想要响应的代码。常见的示例是接收股票市场定价数据的应用程序或聊天应用程序。

  4. IAsyncEnumerable适用于当您有一个集合,其中每个项目都需要或生成异步任务时。一个示例:迭代项目集合并为每个项目执行某种唯一的数据库查询。如果您Transform实际上是一个 I/O 绑定的异步方法,那么这可能更明智。