我有一个简单的同步方法,如下所示:
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
/ await
或IEnumerable
的专家。但是,根据我的理解,使用这种方法“杀死”了IEnumerable
的好处,因为在整个集合加载之前等待任务,因此省略了IEnumerable
集合的“懒惰”。 / p>
在其他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()
- 因此它无法同时使用yield
和async
。我没有更深入地阅读Rx.NET的文档,所以我可能只是错误地使用了库。但我不想花时间学习一个新框架来修改单个方法。
使用新的possibilities in C# 7,现在可以实现自定义类型。因此,理论上,我可以实现IAsyncEnumerable
,它将定义GetEnumerator()
和GetAwaiter()
方法。但是,根据我之前的经验,我记得创建GetEnumerator()
的自定义实现的尝试失败...我最终得到了一个隐藏在容器中的简单List。
因此,我们有4种可能的方法来解决任务:
IEnumerable
IEnumerable
包裹在Task<T>
IAsyncEnumerable
醇>
每次尝试的好处和缺点是什么?哪一项对资源利用率影响最大?
答案 0 :(得分:2)
- 保持代码同步,但使用IEnumerable
- 将其更改为异步,但将IEnumerable包装在Task
中- 学习和使用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绑定的异步方法,那么这可能更明智。