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
方法必须返回Task
s或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是基于推送的,因此消费代码(理想情况下)是反应性的.数据流具有演员感觉,因此它们更独立,再次将结果推送到消费代码.