迭代时如何突破 IAsyncEnumerable?

Posted

技术标签:

【中文标题】迭代时如何突破 IAsyncEnumerable?【英文标题】:How to break out of an IAsyncEnumerable when iterating? 【发布时间】:2019-05-23 04:25:03 【问题描述】:

C# 8 增加了对异步迭代器块的支持,因此您可以等待并返回 IAsyncEnumarator 而不是 IEnumerable

public async IAsyncEnumerable<int> EnumerateAsync() 
    for (int i = 0; i < 10; i++) 
        yield return i;
        await Task.Delay(1000);
    

使用如下所示的非阻塞消费代码:

await foreach (var item in EnumerateAsync()) 
    Console.WriteLine(item);

这将导致我的代码运行大约 10 秒。但是,有时我想在所有元素被消耗之前突破await foreach。但是,使用break,我们需要等到当前等待的Task.Delay 完成。 我们如何在不等待任何悬空异步任务的情况下立即跳出循环?

【问题讨论】:

你是说yield break; 在异步枚举中不起作用? yield break 将按预期工作,但在等待 Task.Delay 时您将无法中断。 如何取消任何可取消的操作,包括Task.Delay,与IAsyncEnumerable无关。这不是悬空任务,同样httpClient.GetStringAsync 不是悬空任务 当我在消费者端使用break 退出foreach 时,默认情况下不会取消等待的任务。 【参考方案1】:

使用CancellationToken 是解决方案,因为这是唯一可以在您的代码中取消Task.Delay 的方法。我们在您的IAsyncEnumerable 中获取它的方式是在创建它时将其作为参数传递,所以让我们这样做:

public async IAsyncEnumerable<int> EnumerateAsync(CancellationToken cancellationToken = default) 
    for (int i = 0; i < 10; i++) 
        yield return i;
        await Task.Delay(1000, cancellationToken);
    

消费方面:

// In this example the cancellation token will be caneled after 2.5 seconds
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2.5));
await foreach (var item in EnumerateAsync(cts.Token)) 
    Console.WriteLine(item);

当然,这将在返回 3 个元素后取消枚举,但将以 TaskCanceledException 结束,从 Task.Delay 中抛出。为了优雅地退出await foreach,我们必须抓住它并在生产端中断:

public async IAsyncEnumerable<int> EnumerateAsync(CancellationToken cancellationToken = default) 
    for (int i = 0; i < 10; i++) 
        yield return i;
        try 
            await Task.Delay(1000, cancellationToken);
         catch (TaskCanceledException) 
            yield break;
        
    

注意

到目前为止,这仍处于预览阶段,可能会发生变化。如果您对此主题感兴趣,可以在IAsyncEnumeration 中watch a discussion of the C# language team 关于CancellationToken

【讨论】:

我只想补充一点,VS2019 和 Core 3 的 preview2 将对CancellationToken 有更好的支持。我们将提供一个.WithCancellationToken() 扩展方法。请参阅 LDM 会议记录:github.com/dotnet/csharplang/blob/master/meetings/2018/… 如果您不使用 EnumeratorCancellation 属性装饰 CancellationToken 参数,Visual Studio 2019 会发出警告。在生产者端抑制TaskCanceledException 似乎也是一种非标准行为。我认为这个异常是在传达有关是否发生取消的信息,因此应该将其传播给调用者。

以上是关于迭代时如何突破 IAsyncEnumerable?的主要内容,如果未能解决你的问题,请参考以下文章

如何安全地迭代 IAsyncEnumerable 以向下游发送集合以批量处理消息

在返回带有取消的 IAsyncEnumerable 的函数中迭代 IAsyncEnumerable

C#8.0: 在 LINQ 中支持异步的 IAsyncEnumerable

如何强制 IAsyncEnumerable 尊重 CancellationToken

如何实现一个高效的 WhenEach 流式传输任务结果的 IAsyncEnumerable?

如何使用 SqlDataReader 返回和使用 IAsyncEnumerable