I was experimenting with how to break out of a ForEachAsync loop. break doesn't work, but I can call Cancel on the CancellationTokenSource. The signature for ForEachAsync has two tokens - one as a stand-alone argument and one in the Func body signature.
I took note that when cts.Cancel() is called, both the token and t variables have IsCancellationRequested set to true. So, my question is: what is the purpose for the two separate token arguments? Is there a distinction worth noting?
List<string> symbols = new() { "A", "B", "C" };
var cts = new CancellationTokenSource();
var token = cts.Token;
token.ThrowIfCancellationRequested();
try
{
await Parallel.ForEachAsync(symbols, token, async (symbol, t) =>
{
if (await someConditionAsync())
{
cts.Cancel();
}
});
catch (OperationCanceledException oce)
{
Console.WriteLine($"Stopping parallel loop: {oce}");
}
finally
{
cts.Dispose();
}
Token passed to the body of the method invoked by
ForEachAsyncis a different one and comes from a internalCancellationTokenSourcewhich will be canceled:t.IsCancellationRequestedset totruewhencts.Cancel()is called)So the purpose of
cancellationToken CancellationTokenargument passed to theParallel.ForEachAsyncis to support cancellation by caller and the one passed to the asynchronous delegate invoked by it - to support cancelation both by external (i.e. caller) and internal sources (see the P.S.).P.S.
Also note that usually it is a good idea to pass and check the token state in your methods (i.e.
await someConditionAsync(t)with corresponding implementation inside) sinceCancelationTokenis used for so called cooperative cancelation.