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 ForEachAsync
is a different one and comes from a internal CancellationTokenSource
which will be canceled:
t.IsCancellationRequested
set to true
when cts.Cancel()
is called)So the purpose of cancellationToken CancellationToken
argument passed to the Parallel.ForEachAsync
is 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) since CancelationToken
is used for so called cooperative cancelation.