You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

释放IAsyncEnumerator未触发底层IAsyncEnumerable取消令牌的解决方案咨询

关于IAsyncEnumerable释放时未触发CancellationToken的问题

经测试发现,释放IAsyncEnumerable生成的IAsyncEnumerator时,底层IAsyncEnumerable的CancellationToken并未被触发。具体表现为以下单元测试的断言失败:

[Test]
public async Task test()
{
    await foreach (var value in TestData())
        break;
}

private static async IAsyncEnumerable<int> TestData([EnumeratorCancellation] CancellationToken ct = default)
{
    try
    {
        yield return 1;
        await Task.CompletedTask;
        yield return 2;
    }
    finally
    {
        Assert.That(ct.IsCancellationRequested, Is.True);
    }
}

我对该行为感到意外,想知道是否有标准方法能在此场景下让TestData的CancellationToken被取消?我可以实现自定义操作符,但这过于繁琐,希望有更优方案。以下是我自行实现的可行方案,但不够理想:

[Test]
public async Task this_test_passes()
{
    await foreach (var value in CancelWhenUnsubscribed(TestData()))
        break;
}

public static async IAsyncEnumerable<T> CancelWhenUnsubscribed<T>(IAsyncEnumerable<T> source, [EnumeratorCancellation] CancellationToken ct = default)
{
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
    await using var en = source.GetAsyncEnumerator(cts.Token);
    using var _ = Disposable.Create(() => cts.Cancel());
    while (await en.MoveNextAsync())
        yield return en.Current;
}

背景说明

我使用System.Interactive.Async合并多个IAsyncEnumerable序列。其实现逻辑为:当合并序列被释放时,会先等待所有进行中的MoveNext任务完成,再释放并终止合并序列。在我的场景中,若合并序列终止时未取消底层IAsyncEnumerable,会导致准死锁,如下测试案例会陷入停滞:

[Test]
public async Task this_test_gets_stuck()
{
    await foreach (var value in AsyncEnumerableEx.Merge([TestData(), TestData()]))
        break;
}

private static async IAsyncEnumerable<int> TestData([EnumeratorCancellation] CancellationToken ct = default)
{
    yield return 1;
    await Task.Delay(TimeSpan.FromHours(100), ct);
    yield return 2;
}

内容的提问来源于stack exchange,提问作者Bogey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 00:45:08