【问题标题】:Should IAsyncEnumerator<T>.DisposeAsync() throw if the last MoveNextAsync() task is not awaited?如果没有等待最后一个 MoveNextAsync() 任务,IAsyncEnumerator<T>.DisposeAsync() 是否应该抛出?
【发布时间】:2021-12-19 03:43:13
【问题描述】:

为什么UseEventsFail 会抛出下面的代码?难道是我在不等待最后一个MoveNextAsync() 任务的情况下处理了异步枚举器?这个例子是我真实程序的简化重现,所以我需要处理异步枚举器来释放它的资源。而Task.CompletedTask 通常是一个Task.Delay(),用作UseEvents() 的超时。如果枚举器任务在超时任务之前完成,则不抛出异常。

异常的堆栈跟踪:

at Program.&lt;&lt;&lt;Main&gt;$&gt;g__GenerateEvents|0_3&gt;d.System.IAsyncDisposable.DisposeAsync()

代码:

// All these are ok
await GenerateEvents().GetAsyncEnumerator().DisposeAsync();
await using var enu = GenerateEvents().GetAsyncEnumerator();
await UseEvents();
await UseEvents2();

// This fail
await UseEventsFail();

async Task UseEvents()
{
    await using var enu = GenerateEvents().GetAsyncEnumerator();
    await Task.WhenAny(enu.MoveNextAsync().AsTask());
}

async Task UseEvents2()
{
    var enu = GenerateEvents().GetAsyncEnumerator();
    await Task.WhenAny(enu.MoveNextAsync().AsTask(), Task.CompletedTask);
}

async Task UseEventsFail()
{
    await using var enu = GenerateEvents().GetAsyncEnumerator();
    await Task.WhenAny(enu.MoveNextAsync().AsTask(), Task.CompletedTask);
}

async IAsyncEnumerable<bool> GenerateEvents()
{
    while (true) {
        await Task.Delay(1000);
        yield return true;
    }
}

【问题讨论】:

    标签: c# .net .net-core async-await iasyncenumerable


    【解决方案1】:

    来自 Stephen Toub 的 MSDN 文章 Iterating with Async Enumerables in C# 8

    很明显,一个MoveNextAsync 调用发生在与先前或后续MoveNextAsync 调用不同的线程上是可以的;毕竟,实现可能会等待一个任务并在其他地方继续执行。然而,这并不意味着MoveNextAsync 是“线程安全的”——远非如此。在给定的异步枚举器上,永远不能同时调用MoveNextAsync,这意味着在上一次调用完成之前,不应在给定的枚举器上再次调用MoveNextAsync。同样,不应在同一枚举器上的 MoveNextAsyncDisposeAsync 仍在运行时调用迭代器上的 DisposeAsync

    所以,不,IAsyncEnumerator&lt;T&gt;s 不支持并发。 IEnumerator&lt;T&gt;s 也是如此。当MoveNext 在另一个线程上运行时,您不能从一个线程调用Dispose。通常枚举(同步或异步)不是线程安全的过程,必须同步。

    【讨论】:

    • 啊,不错的发现:)
    • 所以我认为取消是正确处理 IAsyncEnumerable 的唯一选择。也就是说,向 GetEnumerator() 提供取消令牌,使异步迭代器可取消 ([EnumeratorCancellation])。然后在 timout 取消迭代器,等待 Task.WhenAny(lastMoveNextAsyncTask),然后调用 await DisposeAsync()
    • @maloo 是的,差不多就是这样。
    猜你喜欢
    • 2017-02-11
    • 2013-08-06
    • 2011-03-19
    • 1970-01-01
    • 2019-11-14
    • 1970-01-01
    • 2022-01-22
    • 2018-07-07
    • 1970-01-01
    相关资源
    最近更新 更多