Search code examples
c#cancellationcancellation-tokenparallel.foreachasync

The need for two cancellation tokens in .NET 6 Parallel.ForEachAsync?


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();
}

Solution

  • 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:

    • on "external" cancelation (that's why you see t.IsCancellationRequested set to true when cts.Cancel() is called)
    • for internal reasons (one that I found - any iteration has thrown an uncaught exception, i.e. fail fast principle is applied).

    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.