Отменить задачу в другом классе C#

У меня проблема с отменойTokenSource в 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();
        }
    }
}

В другом классе нужно определить, отменяется ли задача таким образом, но она не работает. Исключение catch никогда не срабатывало

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();
    }

Кто-нибудь знает, почему это не работает, и в этом случае решение?


person JeromeF99    schedule 25.11.2020    source источник
comment
не работает Что не работает? Ошибка компилятора? Исключение? Поведение отличается от ожидаемого?   -  person SomeBody    schedule 25.11.2020
comment
Задача не отменяется. исключение catch никогда не запускается   -  person JeromeF99    schedule 25.11.2020
comment
Связанный: Использование CancellationToken для тайм-аута в Task.Run не работает работа   -  person Theodor Zoulias    schedule 25.11.2020


Ответы (4)


Есть ли у clock.Delay(BUILT_TIME) перегрузка, которая принимает CancellationToken? Если это так, используйте это.

Проблема в том, что если код уже ожидает clock.Delay(BUILT_TIME) при отмене, clock.Delay не будет знать, что ему нужно создать исключение.

person Ilian    schedule 25.11.2020

Я не вижу нигде вызова CancelBuilt() + вы должны вызвать BuildTokenSource.Token.ThrowIfCancellationRequested(), чтобы вызвать OperationCanceledException исключение

token.Cancel() следует вызывать вне асинхронного метода, использующего CancelationToken. Также использование асинхронного метода должно вызывать (обычно на каждом этапе) Token.ThrowIfCancellationRequested();

person HadascokJ    schedule 25.11.2020

Я считаю, что использование посредника (шаблон проектирования посредника) будет более подходящим. Это практически модель pub-sub, которая опубликует событие об отмене и проинформирует всех подписчиков.

Первый класс будет иметь экземпляр посредника в качестве ссылки (поле только для чтения, свойство) для публикации события отмены, а другой должен иметь тот же экземпляр в качестве ссылки, чтобы быть проинформированным, когда событие действительно имеет место. Еще один момент, который вы должны принять, — это подписка, которая должна быть отменена, когда экземпляр класса, содержащий метод GetBuildingOfUserTask, уничтожается.

Что вы думаете?

person Fotis Spatharakis    schedule 25.11.2020

Я согласен с ответом HadascokJ, и я хотел бы внести больше света.

У вас есть основная задача, начатая в Task buildingService.buildTask, и подчиненная, начатая в await clock.Delay(BUILT_TIME);.

Первая задача управляет CancellationToken, а подчиненная — нет. Чтобы лучше понять, замените clock.Delay(BUILT_TIME) на Task Task.Delay(int millisecondsDelay, CancellationToken cancellationToken); и предоставьте, конечно же, CancelationToken. Вы увидите, что в этом случае подчиненная Задача будет отменена. Также вызывая void CancellationToken.CancelAfter(int millisecondsDelay)

Поскольку вы не предоставляете CancellationToken подчиненной Задаче, если основная Задача запущена, ни основная, ни ее подчиненная задача не будут отменены.

С другой стороны, чтобы отменить выполнение подчиненной задачи, предоставьте подчиненной задаче некоторую логику для управления CancelationToken в соответствующем методе и вызовите CancelationToken.ThrowIfCancellationRequested() всякий раз, когда это необходимо, что выдает OperationCanceledException.

По крайней мере, попробуйте разбить длинную задачу на несколько маленьких. Я использую для управления async. Задачи, которые должны запускаться последовательно в Очередь задач, способных отслеживать эти TaskStatus. Чтобы справиться с этим, у меня есть одна реализация на github, если вам нужно. Я называю это FifoTaskQueue

person Trekatz    schedule 31.03.2021