Search code examples
c#.netmultithreadingthreadpoolcancellationtokensource

Adding abort all tasks using a single cancellation token


I received a service who can perform many tasks simultaneously. I was assigned to add an abort option which should end all the current running tasks. Because the system was already implemented, passing CancellationTokenSource to every task was problematic, so what I did was create a single one that all the tasks will listen to for cancellation. when abort recive, that token would send cancellation and then a new CancellationTokenSource instance would be created. I;m having trouble verifying all the tasks until the abort were ended before a new instance of CancellationTokenSource was initiated.

A couple of Q:

  1. At first I thought that TokenSource.Cancel() is just an event sent but it seems to be stuck "on high", so any new tasks with the same token will receive cancellation too, is it true?

  2. ideally, for every task will have his own cancellation token (right?), the thing is who need to hold that token? who invoked the task or the task itself? because once sent cancellation, I'll need to keep that token forever since I can't tell when the task will reach the exit point..

  3. Is there an already implemented way to monitor all the current running task so when a cancel sent I'll make sure all tasks were ended before allowing any new tasks? I did it using a semaphore but it looks like a terrible sulotion..

Sorry for the newbie questions, I already implemented what I needed and it works, but the quality is way below standard and I wish to improve it the right way.


Solution

    1. The task did not receive any cancellation. Its is just a state change of the token. The task has to watch the token, if there is a cancellation request then the task may cancel the operation if possible.

    2. Each task has its own token, because CancellationToken is a struct and will be copied by value. For watching the token (see 1.) the task had to keep the token.

    3. No, you have to await the tasks and you cannot force the tasks to cancel. With the Cancel operation you only send a request for cancellation and a running task may cancel or finish successfully (depends on implementation). Only not started yet tasks (waiting to run) will be canceled by the scheduler (they will not start at all)

    As an example how to implement such an cancellation you are looking for here a very simple sample

    The original service

    public interface IFooService
    {
        Task DoAsync( CancellationToken cancellationToken );
    }
    

    and the one that will handle the cancellation

    public interface ICancelableFooService : IFooService
    {
        Task CancelAsync();
    }
    
    public class CancelableFooService : ICancelableFooService
    {
        private readonly IFooService _foo_service;
        private readonly object _sync = new object();
        private List<Task> _createdtasks = new List<Task>();
        private CancellationTokenSource _cts = new CancellationTokenSource();
    
        public CancelableFooService(IFooService foo_service)
        {
            _foo_service = foo_service;
        }
    
        public async Task CancelAsync()
        {
            _cts.Cancel();
            var t = Task.WhenAll( _createdtasks );
            try
            {
                await t;
            }
            catch { /* we eat all exceptions here */ }
            lock( _sync )
            {
                _cts = new CancellationTokenSource();
                _createdtasks.Clear();
            }
        }
    
        public Task DoAsync( CancellationToken cancellationToken )
        {
            lock(_sync)
            {
                var cts = CancellationTokenSource.CreateLinkedTokenSource( _cts.Token, cancellationToken );
                var token = cts.Token;
                var task = _foo_service.DoAsync( token );
                _createdtasks.Add( task );
            }
            return task;
        }
    }