Why is the cancellation of the task in the caller’s thread canceled?

I found a problem with the task invalidation pattern, and I would like to understand why this should work this way.

Consider this small program where a secondary thread performs an asynchronous "long" task. At the same time, the main thread notifies of cancellation.

The program is a very simplified version of a larger version, which can have many parallel threads performing a “lengthy task”. When the user asks for cancellation, the entire working task must be canceled, therefore the CancellationTokenSource collection.

class Program
{
    static MyClass c = new MyClass();

    static void Main(string[] args)
    {
        Console.WriteLine("program=" + Thread.CurrentThread.ManagedThreadId);
        var t = new Thread(Worker);
        t.Start();
        Thread.Sleep(500);
        c.Abort();

        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }

    static void Worker()
    {
        Console.WriteLine("begin worker=" + Thread.CurrentThread.ManagedThreadId);

        try
        {
            bool result = c.Invoker().Result;
            Console.WriteLine("end worker=" + result);
        }
        catch (AggregateException)
        {
            Console.WriteLine("canceled=" + Thread.CurrentThread.ManagedThreadId);
        }
    }


    class MyClass
    {
        private List<CancellationTokenSource> collection = new List<CancellationTokenSource>();

        public async Task<bool> Invoker()
        {
            Console.WriteLine("begin invoker=" + Thread.CurrentThread.ManagedThreadId);

            var cts = new CancellationTokenSource();
            c.collection.Add(cts);

            try
            {
                bool result = await c.MyTask(cts.Token);
                return result;
            }
            finally
            {
                lock (c.collection)
                {
                    Console.WriteLine("removing=" + Thread.CurrentThread.ManagedThreadId);
                    c.collection.RemoveAt(0);
                }
                Console.WriteLine("end invoker");
            }
        }

        private async Task<bool> MyTask(CancellationToken token)
        {
            Console.WriteLine("begin task=" + Thread.CurrentThread.ManagedThreadId);
            await Task.Delay(2000, token);
            Console.WriteLine("end task");
            return true;
        }

        public void Abort()
        {
            lock (this.collection)
            {
                Console.WriteLine("canceling=" + Thread.CurrentThread.ManagedThreadId);
                foreach (var cts in collection) //exception here!
                {
                    cts.Cancel();
                }
                //collection[0].Cancel();
            };
        }

    }
}

, , , . , , .

"foreach" :

        public void Abort()
        {
            lock (this.collection)
            {
                Console.WriteLine("canceling=" + Thread.CurrentThread.ManagedThreadId);
                //foreach (var cts in collection) //exception here!
                //{
                //    cts.Cancel();
                //}
                collection[0].Cancel();
            };
        }

, , . , :

program=10
begin worker=11
begin invoker=11
begin task=11
canceling=10
removing=10
end invoker
Press any key...
canceled=11

-, "finally" , "Invoker" .

"finally" ?

+4
2

, . , , , . TaskScheduler.FromCurrentSynchronizationContext(). , .

Task , . , . threadpool , , .

, Abort() . ( ) , finally . , . , , .

CancelAfter(), finally, , finally TP.

+4

, , Cancel() , await / . catch , , TaskCancelationException,

try
{
    bool result = await c.MyTask(cts.Token);
    return result;
}
catch (Exception exception)
{
    Console.WriteLine("catch invoker exception=" + exception.GetType());
    Console.WriteLine("catch invoker=" + Thread.CurrentThread.ManagedThreadId);
    return true;
}

,

program=10
begin worker=11
begin invoker=11
begin task=11
canceling=10
catch invoker exception=TaskCanceledException
catch invoker=10      <-- parent thread resuming on child cancellation
removing=10

, , - , ( ); , ( c.Abort();), await , ,

program=10
begin worker=11   <-- first child thread
begin invoker=11
begin task=11
Press any key...
end task=12       <-- second child thread resuming on 'await Task.Delay'
removing=12       <-- second child thread resuming on 'await c.MyTask(cts.Token)'
end invoker=12
end worker=True    
end worker=11     <-- back to the first child thread

thread 11, ( Worker), , MyTask, thread 12 ( ), , Invoker, thread 11 , .

0

Source: https://habr.com/ru/post/1525057/


All Articles