IAsyncEnumerator with WithCancellation extension method #111758
-
The code below is iterating an A cancellation token can be passed directly to When I run the code below, it outputs all 10 integers on the console. When using the I assume this is by design: What were the decision drivers against the iterator cooperatively cancelling the iteration? var token = new CancellationToken(true);
await foreach(var item in RangeAsync().WithCancellation(token))
Console.WriteLine(item);
async IAsyncEnumerable<int> RangeAsync([EnumeratorCancellation]CancellationToken token = default)
{
for(int i = 0; i < 10; i++)
{
await Task.Delay(100);
yield return i;
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
The sole purpose of WithCancellation is to pass the provided token into the GetAsyncEnumerator method. That's it, nothing more, nothing less. With [EnumeratorCancellation], the compiler then generates an implementation for the async iterator that ensures the CancellationToken passed into GetAsyncEnumerator is passed into the body of the iterator as the tagged CancellationToken parameter. Again, nothing more, nothing less. Well, a little more, in that it needs to handle the case where a different CancellationToken is passed into the exposed iterator entry point; in that case, it combines the tokens into a new one, and that's what's passed into the body of the iterator as the CancellationToken argument.
It doesn't try to somehow instrument the iterator method with non-visible checks against the passed in token for the same reason it doesn't generate arbitrary checks in a normal async method. And similarly, it doesn't automatically thread the cancellation token parameter to any APIs you call that accept a CancellationToken, for the same reasons it doesn't in async methods. If your question is instead why doesn't the compiler generate code for the iterator's MoveNextAsync that polls the token, there are multiple reasons. Philosophically, CancellationToken is a cooperative model, and the implementer of the async iterator should have the chance to decide where cancellation occurs, or doesn't occur. Practically, the developer of the iterator should be able to react to cancellation, e.g. in a catch block that handles when cancellation occurs... they can do that if they're in complete control of everywhere cancellation can take effect, e.g. wrapping a method they've passed the token to or where they've explicitly polled, but they have no good opportunity to do so if it's just in a preamble of MoveNextAsync. Any further discussion of the code generated by the compiler would also be better in dotnet/roslyn. |
Beta Was this translation helpful? Give feedback.
The sole purpose of WithCancellation is to pass the provided token into the GetAsyncEnumerator method. That's it, nothing more, nothing less.
With [EnumeratorCancellation], the compiler then generates an implementation for the async iterator that ensures the CancellationToken passed into GetAsyncEnumerator is passed into the body of the iterator as the tagged CancellationToken parameter. Again, nothing…