Search code examples
c#taskcancellation-token

Stop a Task with a CancelationToken


I have a class that represents a custom Task. This class has an Execution method that returns the IEnumerable<string> of all the inside yield return statement, and a Start method that calls the enumerable, thus executing the lazy code.

My problem is that I have also a Stop method that basically cancels the token associated with the Start method by using:

TokenSource.CancelAfter(delay);
TokenSource.Token.ThrowIfCancellationRequested();

This code actually stops the running task, but how can I handle the exception? This is not done inside the actual Task.

Any ideas on how to handle the stop?


An example I've been using to test this part:

public class FunctionTask : Awaitable
{
    public FunctionTask(string code, Scheduler scheduler = null) 
    : base(code, scheduler)
    { }

    public override IEnumerable<string> Execution()
    {
         for (int i = 0; i < 10; i++)
         {
             yield return WaitFor(TimeSpan.FromSeconds(10d))
                 .WithDescription($"Step {i + 1}, waiting 1 second");
         }

        yield return "Done";
    }
}

And these are the (inherited) start and stop methods:

public Task Start()
{
    stopRequested = false;
    Status.Value = TaskStatus.WaitingToRun;

    Task task = new Task(() =>
        {
            Status.Value = TaskStatus.Running;

            try
            {
                // Iterate through the Execution task state
                IEnumerable<string> executionState = Execution();
                foreach (string state in executionState)
                    WaitState.Value = state;

                // And then through the termination task state
                IEnumerable terminationState = Termination();
                foreach (string state in terminationState)
                    WaitState.Value = state;

                Status.Value = TaskStatus.RanToCompletion;
            }
            catch (Exception ex) // This may be caused by a stop request or an actual exception
            {
                Status.Value = TaskStatus.Faulted;

                if (stopRequested)
                    Logger.Error(ex);
                else
                    Logger.Warn($"Task with code {Code} faulted because a stop has been requested");
            }
        },
        TokenSource.Token
    );

    task.Start(Scheduler);
    return task;
}

public virtual void Stop()
{
    if (Status.Value == TaskStatus.Running || Status.Value == TaskStatus.WaitingToRun)
    {
        stopRequested = true;

        TokenSource.Cancel();
        TokenSource.Token.ThrowIfCancellationRequested(); // Let the task fail and then stop

        Status.Value = TaskStatus.Canceled;
    }
}


Solution

  • Use the property IsCancellationRequested if you don't want to throw the OperationCanceledException.

    In the Stop method just call TokenSource.Cancel() and remove the TokenSource.Token.ThrowIfCancellationRequested(), it doesn't make sense there.

    Then use the IsCancellationRequested property to stop the enumeration, something like:

    foreach (string state in executionState)
    {
        if(token.IsCancelltionRequested)
            break;
    
        WaitState.Value = state;
    }