简体   繁体   English

应该 IAsyncEnumerator<T> 如果不等待最后一个 MoveNextAsync() 任务,.DisposeAsync() 抛出?

[英]Should IAsyncEnumerator<T>.DisposeAsync() throw if the last MoveNextAsync() task is not awaited?

Why does the UseEventsFail throw in the code below?为什么UseEventsFail在下面的代码中抛出? Could it be that I dispose the async enumerator without awaiting the last MoveNextAsync() task?可能是我在不等待最后一个MoveNextAsync()任务的情况下处理了异步枚举器吗? This example is simplified repro of my real program, so I need to dispose the async enumerator to release its resources.这个例子是我真实程序的简化复制,所以我需要处理异步枚举器来释放它的资源。 And Task.CompletedTask is usually a Task.Delay() used as a timeout for UseEvents() .Task.CompletedTask通常是一个Task.Delay()用作UseEvents()的超时。 If the enumerator task completes before the timeout task, then no exception is thrown.如果枚举器任务在超时任务之前完成,则不会引发异常。

Stack trace of exception:异常的堆栈跟踪:

at Program.<<<Main>$>g__GenerateEvents|0_3>d.System.IAsyncDisposable.DisposeAsync()

Code:代码:

// 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;
    }
}

From the MSDN article Iterating with Async Enumerables in C# 8 by Stephen Toub:从 MSDN 文章Iterating with Async Enumerables in C# 8 by Stephen Toub:

It should be evident that it's fine for one MoveNextAsync call to occur on a different thread from a previous or subsequent MoveNextAsync call;很明显,一个MoveNextAsync调用发生在与前一个或后续MoveNextAsync调用不同的线程上是正常的; after all, the implementation may await a task and continue execution somewhere else.毕竟,实现可能会等待一个任务并在其他地方继续执行。 However, that doesn't mean MoveNextAsync is “thread-safe”—far from it.然而,这并不意味着MoveNextAsync是“线程安全的”——远非如此。 On a given async enumerator, MoveNextAsync must never be invoked concurrently, meaning MoveNextAsync shouldn't be called again on a given enumerator until the previous call to it has completed.在给定的异步枚举, MoveNextAsync绝不能并发调用,这意味着MoveNextAsync不应直到它的前一个调用已完成再上一个给定的枚举调用。 Similarly, DisposeAsync on an iterator shouldn't be invoked while either MoveNextAsync or DisposeAsync on that same enumerator is still in flight.同样, DisposeAsync上的迭代器不应该同时调用任何MoveNextAsyncDisposeAsync在同一枚举仍在飞行。

So, no, IAsyncEnumerator<T> s do not support concurrency.所以,不, IAsyncEnumerator<T>不支持并发。 The same is true for IEnumerator<T> s. IEnumerator<T>也是如此。 You can't call Dispose from one thread while a MoveNext is running on another thread.MoveNext在另一个线程上运行时,您不能从一个线程调用Dispose Enumerating in general (synchronously or asynchronously) is not a thread-safe procedure, and must be synchronized.枚举一般(同步或异步)不是线程安全的过程,必须同步。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM