My question is shouldn I use the cancellation token in just one place?
Cancellation is cooperative, so in order to be able to cancel, you had to implement cancellation in the producer code GetLines
, the one that provides the IAsyncEnumerable<Line>
. So, producer is one place.
Now, imagine that the method that the code that does something with that data is named ConsumeLines
, let's say it's a consumer. In your case it could be one codebase, but generally speaking, it could be another library, another repo, another codebase.
In that other codebase, there is no guarantee they have the same CancellationToken
.
So, how can a consumer cancel?
Consumer needs to pass a CancellationToken
to the IAsyncEnumerable<T>.GetAsyncEnumerator
, but it's not directly exposed if you're using await foreach
construct.
To solve this, WithCancellation
extension method was added. It simply forwards the CancellationToken
passed to it to the underlying IAsyncEnumerable
by wrapping it in a ConfiguredCancelableAsyncEnumerable.
Depending on several conditions, this CancellationToken
is linked to the one in the producer using CreateLinkedTokenSource so that consumer can cancel using cooperative cancellation implemented in the producer so not only we can cancel consuming, but also, producing.
Should the cancellation token be acted upon before yielding the record or is the IAsyncEnumerable.WithCancellation() basically doing the same thing? What is the difference if any?
Yes, you should act upon your CancellationToken
by using either IsCancellationRequested or ThrowIfCancellationRequested in your producer code. Cancellation is cooperative, if you don't implement it in producer, you won't be able to cancel producing the values of IAsyncEnumerable
.
As to when exactly to cancel - before or after yielding - it totally up to you, the idea is to avoid any unnecessary work. In this spirit, you could also check for cancellation in the first line of your method, to avoid sending an unnecessary http request.
Remember that cancelling the consuming of the values, is not necessarily the same thing as cancelling the producing of the values.
Again, producer and consumer could be in different codebases, and could be using CancellationTokens
from different CancellationTokenSources
.
To link those different CancellationTokens
together you need to use the EnumeratorCancellation
attribute.
Please read a more in-depth explanation in my article EnumeratorCancellation: CancellationToken parameter from the generated IAsyncEnumerable.GetAsyncEnumerator will be unconsumed