When I use CancelAfter(), the Task is still running

后端 未结 3 1293
[愿得一人]
[愿得一人] 2021-01-13 20:31

I want to use CancellationTokenSource stop the Task. My tests as follow:

Test 1 : Using Cancel() stopped the task sucessfully.

Test 2 : Using CancelAfter() d

相关标签:
3条回答
  • 2021-01-13 21:06

    The CancelationToken is kind of a misleading. The problem is that if Task is already started it cannot be stopped unless you explicitly check CancelationToken, for example CancellationToken.ThrowIfCancellationRequested. The purpose of the CancelationToken is to prevent the Task to start while it is still scheduled.

    That is difference in your example, with Cancel you cancel the task while it is still scheduled but with CancelAfter task is already started and there is no way to stop it anymore.

    First, the CancellationToken. If you cancel the token before the continuation is scheduled, then the continuation delegate never actually runs - it’s cancelled. However, note that the token does not cancel the continuation once it has started. In other words, the CancellationToken cancels the scheduling of the continuation, not the continuation itself. For this reason, I think the CancellationToken parameter is misleading, and I never use it myself.

    https://blog.stephencleary.com/2015/01/a-tour-of-task-part-7-continuations.html

    Part explaining the continuation of the Task but it is the same also with the Task itself, continuation will schedule new task anyway.

    CancellationTokenSource source = new CancellationTokenSource();
    CancellationToken token = source.Token;
    
    //If cancelAfter > taskWaiting - task will be finished
    //If canceAfter < taskWaiting - task will be canceled
    //If cancelAfter == taskWaiting - unexpected :)
    int cancelAfter = 100;
    int taskWaiting = 200;          
    
    //If token is canceled before run of the task it won't start at all
    //source.Cancel();
    
    //Register the cancel action
    token.Register(() =>
    {
        Console.WriteLine("Task is canceled");
    });
    
    Task.Run(() =>
    {
        Console.WriteLine("Action is started");
        Thread.Sleep(taskWaiting);
    
        if (token.IsCancellationRequested)
            return;             
    
        Console.WriteLine("Action is finished");
    }, token);          
    
    source.CancelAfter(cancelAfter);
    
    0 讨论(0)
  • 2021-01-13 21:18

    Cancel() succeeds in cancelling that task, because task had no chance to even start. When task is scheduled and it about to run - there is a check if its not already cancelled. You can verify it by modifying testFun like this:

    static Action testFun = () => {
        Console.WriteLine("start");
        Thread.Sleep(4000); // or other a long time operation
        Console.WriteLine("Action is end");
    };
    

    And observe that when you use Cancel - there is no "start" output to console, so task has not been started.

    When you introduce a delay with CancelAfter, or by just doing:

    Task task = new Task(testFun, token);
    task.Start();
    Thread.Sleep(10); // < small delay
    source.Cancel();
    

    Task has a chance to start, and after it has been started - cancelling token has no effect, because nothing in the body of testFunc checks if token has been cancelled. It's not possible for task to be magically cancelled in the middle of execution without cooperation from that task code.

    Cooperation can look for example like this (though using Task.Delay in this way is not usually recommended):

    static Action<CancellationToken> testFun = (CancellationToken ct) => {
        Console.WriteLine("start");
        Task.Delay(4000, ct).Wait();
        Console.WriteLine("Action is end");
    };
    

    Then when you start task - you pass cancellation token there:

    Task task = new Task(() => testFun(token), token);
    

    And now there is cooperation - Task.Delay will notice that token is cancelled and will cancel Task.Delay operation, which in turn will cancel your task (by throwing OperationCanceledException).

    Same can be done with async\await:

    static async Task TestFun(CancellationToken ct) {
        Console.WriteLine("start");
        await Task.Delay(4000, ct);
        Console.WriteLine("Action is end");
    }
    
    Task task = TestFun(token);
    // no need for task.Start() here - task is already started
    source.CancelAfter(100);
    Console.ReadLine();
    
    0 讨论(0)
  • 2021-01-13 21:21

    The core problem is the Task won't stop once it is started.

    It must be check the IsCancellationRequested.

    There is one way to solve this problem:

    when task is running , use Thread.CurrentThread to get the thread, and create a new Task to listen the running task's CancellationToken. In the listening function:listening the status of cancel, call thread.Abort() if cancel event is ture. Use Abort() is a unsafe way,but it can stop the task.

    The code as follow:

        static void Main(string[] args)
        {
            // If the task running used 3000ms, stop.
            int timeOut = 3000;
            CancellationTokenSource source = new CancellationTokenSource();
            source.CancelAfter(timeOut);
            CancellationToken token = source.Token;
            token.Register(() =>
            {
                Console.WriteLine("Task Is TimeOut!!!!! Stop");
            });
            //start the task:
            Task.Run(() =>
            {
                Console.WriteLine("Task start");
                Thread thread = Thread.CurrentThread;
                //create a new task listening the token;
                Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("Listening start");
                    while (!token.IsCancellationRequested)
                    {
                        Console.WriteLine("Listening...");
                        Thread.Sleep(800);
                    }
                    Console.WriteLine("Listening End");
                    thread.Abort();
                }, token);
    
                Stopwatch time = Stopwatch.StartNew();
                #region A long time operation:;
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("run...");
                    Thread.Sleep(100);
                }
                #endregion
                time.Stop();
                Console.WriteLine("Task end. cost:{0}", time.ElapsedMilliseconds);
                source.Cancel();
                Console.WriteLine("Task End");
                token.ThrowIfCancellationRequested();
            }, token);
            Console.ReadLine();
        }
    
    0 讨论(0)
提交回复
热议问题