我可以等待我用发电机创建的可枚举数吗?

Ben*_*aum 13 c# asynchronous generator promise async-await

假设我有异步获得的整数序列.

async Task<int> GetI(int i){
    return await Task.Delay(1000).ContinueWith(x => i);
}
Run Code Online (Sandbox Code Playgroud)

我想在该序列上创建一个生成器,如果序列是同步的,我会这样做:

IEnumerable<int> Method()
{
    for (var i = 0; i < 100; i++)
    {
        yield return GetI(i); // won't work, since getI returns a task
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,我认为类比是让生成器异步并从中产生:

async Task<IEnumerable<int>> Method()    
{
    for (var i = 0; i < 100; i++)
    {
        yield return await Task.Delay(1000).ContinueWith(x => i);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是行不通的,因为一个yield必须返回某个IEnumerable东西的方法,替代方案,这更有意义IEnumerable<Task<int>>但是由于async方法必须返回Tasks或void,所以不能编译.

现在,我意识到我可以简单地删除await并返回一个,IEnumerable<Task<int>>但这对我没有帮助,因为迭代将在任何数据准备好之前继续询问数据,因此它不能解决我的问题.

  • 有没有什么方法可以很好地混合可枚举和任务与语言给我等待和收益的好糖?
  • 有没有办法很好地消费它?

(从网上搜索,我怀疑回答的第一个问题是假的,第二个是一个观察者/可观察的,但我找不到任何标准基准和我感兴趣的最好的方式来实现在C#这种模式)

Ste*_*ary 13

异步序列很有趣.根据您的具体要求,有许多不同的方法.我并不完全清楚你想要的语义,所以这些是一些选择.

Task<IEnumerable<T>>是一个异步检索的集合.只有一个任务 - 一个异步操作 - 检索整个集合.这听起来不像你想要的.

IEnumerable<Task<T>>是(异步)数据的(同步)序列.有多个任务,可能同时也可能不同时处理.有几种方法可以实现这一点.一个是使用枚举器块并产生任务; 每次从枚举中检索下一个项目时,此方法将启动一个新的异步操作.或者,您可以创建并返回一组任务,其中所有任务同时运行(这可以通过LINQ Select后跟ToList/ 来优先完成源序列ToArray).然而,这有一些缺点:无法异步确定序列是否已经结束,并且在返回当前项之后立即开始下一个项处理并不容易(这通常是期望的行为).

核心问题IEnumerable<T>是本质上是同步的.有几种解决方法.一个是IAsyncEnumerable<T>,它是Ix-Async NuGet包中的异步等效IEnumerable<T>和可用的.不过,这种方法有其自身的缺点.当然,你失去了很好的语言支持(即枚举器块和).而且,"异步可枚举"的概念并不完全相同; 理想情况下,异步API应该是厚实的而不是繁琐的,并且可用的数据非常繁琐.这里有关于原始设计的更多讨论,以及这里粗略/讨厌的考虑因素.IEnumerable<T>foreach

因此,现在更常见的解决方案是使用可观察量数据流(两者也可通过NuGet获得).在这些情况下,你必须将"序列"视为具有自己生命的东西.Observable是基于推送的,因此消费代码(理想情况下)是反应性的.数据流具有演员感觉,因此它们更独立,再次将结果推送到消费代码.