我可以等待我用生成器创建的枚举吗? [英] Can I await an enumerable I create with a generator?

查看:12
本文介绍了我可以等待我用生成器创建的枚举吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个异步获取的整数序列.

Let's say I have a sequence of integers I obtain asynchronously.

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

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

I want to create a generator over that sequence, if the sequence was synchronous I'd do:

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

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

So, I figured the analogy is making the generator async and yielding from it:

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

这行不通,因为带有 yield 的方法必须返回一个 IEnumerable 的东西,另一种更有意义的选择是 IEnumerable 但这不会编译,因为 async 方法必须返回 Tasks 或 void.

This won't work, since a method with yield must return an IEnumerable of something, the alternative, which makes more sense is IEnumerable<Task<int>> but that won't compile since async methods must return Tasks or void.

现在,我意识到我可以简单地删除等待并返回一个 IEnumerable> 但这对我没有帮助,因为迭代会在任何数据之前不断询问数据准备好了,所以它不能解决我的问题.

Now, I realize I can simply remove the await and return an IEnumerable<Task<int>> but that won't help me since the iteration will keep asking for data before any of it is ready, so it doesn't solve my issue.

  • 有什么方法可以将可枚举和任务与语言给我的 await 和 yield 的美好结合起来吗?
  • 有什么方法可以很好地消耗它吗?

(从网上搜索,我怀疑第一个问题的答案是错误的,第二个问题是观察者/可观察者,但我找不到任何规范参考,我对最好的方法感兴趣在 C# 中实现这个模式)

推荐答案

异步序列很有趣.有许多不同的方法,具体取决于您想要做什么.我不完全清楚您想要的语义,所以这些是一些选项.

Asynchronous sequences are interesting. There's a number of different approaches, depending on exactly what you want to do. I'm not entirely clear on your desired semantics, so these are some of the options.

Task> 是一个异步检索的集合.只有一项任务——一项异步操作——可以检索整个集合.这听起来不像你想要的.

Task<IEnumerable<T>> is an asynchronously-retrieved collection. There is only one task - one asynchronous operation - that retrieves the entire collection. This does not sound like it's what you want.

IEnumerable> 是(异步)数据的(同步)序列.有多个任务,它们可能会或可能不会同时处理.有几个选项可以实现这一点.一种是使用枚举器块并产生任务;每次从可枚举项中检索下一项时,此方法将启动一个新的异步操作.或者,您可以创建并返回所有任务同时运行的任务集合(这可以通过 LINQ 的 Select 后跟 ToList/ 在源序列上优雅地完成ToArray).但是,这有两个缺点:没有办法异步判断序列是否已经结束,并且在返回当前项后不容易立即开始next项处理(这通常期望的行为).

IEnumerable<Task<T>> is a (synchronous) sequence of (asynchronous) data. There are multiple tasks, which may or may not all be processing simultaneously. There are a couple of options for implementing this. One is using an enumerator block and yielding tasks; this approach will start a new asynchronous operation each time the next item is retrieved from the enumerable. Alternatively, you can create and return a collection of tasks with all tasks running concurrently (this can be done elegantly over a source sequence via LINQ's Select followed by ToList/ToArray). However, this has a couple of drawbacks: there is no way to asynchronously determine if the sequence is already ended, and it's not easy to immediately start the next item processing after returning the current item (which is commonly desired behavior).

核心问题是 IEnumerable 本质上是同步的.有几种解决方法.一个是 IAsyncEnumerable,它是 IEnumerable 的异步等价物,可在 Ix-Async NuGet 包.但是,这种方法有其自身的缺点.当然,您失去了对 IEnumerable(即枚举器块和 foreach)的良好语言支持.此外,异步可枚举"的概念本身并不完全具有性能;理想情况下,异步 API 应该是笨拙的而不是健谈的,并且可枚举非常健谈.关于 此处为原创设计,以及这里是笨拙/健谈的注意事项.

The core problem is that IEnumerable<T> is inherently synchronous. There are a couple of workarounds. One is IAsyncEnumerable<T>, which is an asynchronous equivalent of IEnumerable<T> and available in the Ix-Async NuGet package. This approach has its own drawbacks, though. Of course, you lose the nice language support for IEnumerable<T> (namely, enumerator blocks and foreach). Also, the very notion of an "asynchronous enumerable" is not exactly performant; ideally, asynchronous APIs should be chunky rather than chatty, and enumerables are very chatty. More discussion on the original design here, and on the chunky/chatty considerations here.

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

So, these days a much more common solution is to use observables or dataflows (both also available via NuGet). In these cases, you have to think of the "sequence" as something with a life of its own. Observables are push-based, so the consuming code is (ideally) reactive. Dataflows have an actor feel, so they act more independent, again pushing results to the consuming code.

这篇关于我可以等待我用生成器创建的枚举吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆