0

I have a problem with the cancellationTokenSource in C#

public class Building {
    public CancellationTokenSource BuildTokenSource;

    public void StartBuilt()
    {
        BuildTokenSource = new CancellationTokenSource();

        buildingService.buildTask = Task.Run(async () =>
        {
            await clock.Delay(BUILT_TIME);
        }, BuildTokenSource.Token);
    }

    public void CancelBuilt()
    {
        if (BuildTokenSource != null)
        {
            BuildTokenSource.Cancel();
        }
    }
}

In another class in want to detect if the task is cancel like this but it doesn't work. The catch exception in never triggered

public async Task<Building> GetBuildingOfUserTask()
    {
        double remainingTime = unitService.GetRemainingTime();

        if (remainingTime <= 2000 && remainingTime > 0.0)
        {
            Building building = GetBuilding();
            CancellationToken cancellation = building.BuildTokenSource.Token;
            try
            {
                await buildTask;
            }
            catch (OperationCanceledException) when (cancellation.IsCancellationRequested)
            {
                return GetBuildingOfUser();
            }
        }
        return GetBuildingOfUser();
    }

Anyone have any idea why this is not working and in this case a solution?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
JeromeF99
  • 19
  • 4
  • 1
    "it doesn't work" What doesn't work? Compiler error? Exception? Different behaviour than expected? – SomeBody Nov 25 '20 at 10:57
  • The task isn't cancel. the catch exception is never triggered – JeromeF99 Nov 25 '20 at 10:59
  • Related: [Using CancellationToken for timeout in Task.Run does not work](https://stackoverflow.com/questions/22637642/using-cancellationtoken-for-timeout-in-task-run-does-not-work) – Theodor Zoulias Nov 25 '20 at 12:39

4 Answers4

2

Does clock.Delay(BUILT_TIME) have an overload that accepts a CancellationToken? If so, use that.

The problem is if the code is already awaiting clock.Delay(BUILT_TIME) when you cancel, clock.Delay would not know that it needs throw an exception.

Ilian
  • 5,113
  • 1
  • 32
  • 41
0

I don't see anywhere calling CancelBuilt() + you must call BuildTokenSource.Token.ThrowIfCancellationRequested() in order to raise OperationCanceledException exception

The token.Cancel() should be called outside the async method consuming the cancelationToken. Also consuming async method must call (usually in every step) Token.ThrowIfCancellationRequested();

JanH
  • 107
  • 1
  • 9
0

Using a Mediator (Mediator design pattern) will be a better fit I believe. This is practically a pub-sub model that will publish an event on cancel and will inform all subscribers.

The first class will have the mediator instance as a reference (a read-only field, a property) to publish the cancel event and the other one should have the same instance as reference in order to be informed when the event actually takes place. Another point that you should take is the subscription that should be cancelled when the instance of the class that contains 'GetBuildingOfUserTask' method is destroyed.

What do you think?

-1

I agree the answer of HadascokJ and I would like to bring more light.

You have a main task started at Task buildingService.buildTask and it subordinated one started at await clock.Delay(BUILT_TIME);

The first Task manages the CancellationToken but the subordinated one not. To figure it better replace your clock.Delay(BUILT_TIME) with a Task Task.Delay(int millisecondsDelay, CancellationToken cancellationToken); and provide, of course the CancelationToken. You will see that, in this case, the subordinated Task will be canceled. Also invoking void CancellationToken.CancelAfter(int millisecondsDelay)

As you do not provide the CancellationToken to the subordinated Task, ones the main Task has been started, neither the main nor its subordinated task will be canceled.

In the other hand, to cancel at execution of the subordinated Task, provide to the subordinated task some logic to manage the CancelationToken into its corresponding method and invoke the CancelationToken.ThrowIfCancellationRequested() whenever is necessary, which throws the OperationCanceledException.

At least, try to split long task into several small ones. I use to manage async. Tasks, those have to be run sequentially into a Queue of task able to observe these TaskStatus. To deal with that, I have one implementation at github if you need. I call it FifoTaskQueue

Community
  • 1
  • 1
Trekatz
  • 9
  • 4