我有一个简单的同步方法,看起来像这样:
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
集合的“惰性”。在其他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 的文档,所以我可能只是错误地使用了该库。但我不想花时间学习一个新框架来修改一个方法。
有了 C# 7 中的新 功能, 现在可以实现自定义类型。因此,理论上,我可以实现一个 IAsyncEnumerable
,它将定义两个方法 GetEnumerator()
和 GetAwaiter()
。然而,从我的以往经验中,我记得曾尝试创建一个自定义的 GetEnumerator()
实现... 最终我得到了一个隐藏在容器中的简单列表。
因此,我们有四种可能的方法来解决这个任务:
- 保持代码同步,但使用
IEnumerable
- 将其改为异步,但在
Task<T>
中包装IEnumerable
- 学习并使用 Rx.NET(System.Reactive)
- 使用 C# 7 特性创建自定义的
IAsyncEnumerable
这些尝试的优点和缺点是什么?哪一个对资源利用有最显著的影响?
src.LoadOofsAsync()
,它已经返回了一个Task<IEnumerable<Oof>>
。再次包装异步操作并不能帮助解决问题,除非Transform
确实很耗费资源,并且可以有意义地并行化处理/等待。 - Jeroen MostertMyMethod
“好”/“更好”,最好也应该首先应用于LoadOofsAsync
,或者至少在那里进行考虑。真正流式处理的解决方案与将初始处理时间减半之间存在很大的区别(尽管这当然不是无用的)。 - Jeroen MostertObservable
(这是最接近完全异步枚举的东西)对代码库的其余部分有相当大的影响(更不用说由此带来的学习曲线)。一些项目实现了具有MoveNextAsync
的IAsyncEnumerator
,但缺点是不支持所有现有的枚举操作(甚至是像foreach
这样简单的操作)。这是在简单和最大响应/酷炫之间的平衡。 - Jeroen Mostert