Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cancel all async methods if one throws an exception

I play with cancelation token, and I would like to understand how this works. I have two async methods(in my example two but in theory I can have 100). I want to cancel work in all async methods if one of them throws an exception.

My idea is to cancel token in exception where all methods are called. When a token is canceled I would expect that other method stop working, but this is not happening.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace CancelationTest
{
    class Program
    {
        static void Main(string[] args)
        {
            new Test();
            Console.ReadKey();
        }
    }

    public class Test
    {
        public Test()
        {
            Task.Run(() => MainAsync());
        }

        public static async Task MainAsync()
        {
            var cancellationTokenSource = new CancellationTokenSource();
            try
            {
                var firstTask = FirstAsync(cancellationTokenSource.Token);
                var secondTask = SecondAsync(cancellationTokenSource.Token);
                Thread.Sleep(50);
                Console.WriteLine("Begin");
                await secondTask;
                Console.WriteLine("hello");
                await firstTask;
                Console.WriteLine("world");
                Console.ReadKey();
            }
            catch (OperationCanceledException e)
            {
                Console.WriteLine("Main OperationCanceledException cancel");
            }
            catch (Exception e)
            {
                Console.WriteLine("Main Exception + Cancel");
                cancellationTokenSource.Cancel();
            }
        }

        public static async Task FirstAsync(CancellationToken c)
        {
            c.ThrowIfCancellationRequested();
            await Task.Delay(1000, c);
            Console.WriteLine("Exception in first call");
            throw new NotImplementedException("Exception in first call");
        }

        public static async Task SecondAsync(CancellationToken c)
        {
            c.ThrowIfCancellationRequested();
            await Task.Delay(15000, c);
            Console.WriteLine("SecondAsync is finished");
        }
    }
}

The second method finish work and delay task for 15 seconds even when the first method throws an exception.

What is result:

Begin

Exception in the first call

SecondAsync is finished

hello

Main Exception + Cancel

I would expect that secondAsync stop delay and throw OperationCancelException. I would expect this result:

Begin

Exception in first call

Main Exception + Cancel

Main OperationCanceledException cancel

Where am I making mistake? Why method SecondAsync is fully executed and doesn't throw an exception? And if I change the order of SecondAsync and FirstAsync than Second method stop to delay when the token is canceled and throw an exception.

like image 693
Raskolnikov Avatar asked Oct 24 '25 23:10

Raskolnikov


1 Answers

Because the relevant part of your code is:

try
{
   ...
   await secondTask;
   await firstTask;
}
catch(...)
{
    source.Cancel();
}

Now while the firstTask is started and has thrown, it is awaited after the secondTask. The exception won't surface in the caller until the task is awaited. And so the catch clause will only execute after secondTask has already completed. The Cancel() is just happening too late.

If you want your firstTask to interrupt the second one you will have to

  1. pass the source into FirstAsync and call Cancel() there. A little ugly.
  2. change the await structure. I think your sample is a little artificial. Use Parallel.Invoke() or something similar and it will happen quite naturally.
like image 135
Henk Holterman Avatar answered Oct 27 '25 13:10

Henk Holterman