Проверьте, был ли отменен CancellationToken

Я создал небольшой демонстрационный проект, чтобы помочь мне понять, как я могу использовать токены отмены. Я понимаю, что вы отменяете токен и проверяете, была ли запрошена отмена, но есть ли способ проверить, была ли отмена реализована? В моем примере ниже я не хочу запускать Work() снова, пока DoWork() не завершит работу.

public class Program
{
    public static CancellationTokenSource tokenSource;

    private static void Main(string[] args)
    {
        while (true)
        {
            Work();
        }
    }

    public static async void Work()
    {
        tokenSource = new CancellationTokenSource();
        Console.WriteLine("Press any key to START doing work");
        Console.ReadLine();
        Console.WriteLine("Press any key to STOP doing work");
        DoWork(tokenSource.Token);
        Console.ReadLine();
        Console.WriteLine("Stopping...");
        tokenSource.Cancel();
    }

    public static async void DoWork(CancellationToken cancelToken)
    {
        while (true)
        {
            Console.WriteLine("Working...");
            await Task.Run(() =>
            {
                Thread.Sleep(1500);
            });

            if (cancelToken.IsCancellationRequested)
            {
                Console.WriteLine("Work Cancelled!");

                return;
            }
        }
    }
}

person David Andrew Thorpe    schedule 08.10.2019    source источник
comment
Не используйте async void. Используйте async Task. Результирующее Task можно ожидать (выбрасывая исключение, если оно было отменено) или явно проверять на завершение, как вам угодно.   -  person Jeroen Mostert    schedule 08.10.2019


Ответы (2)


Вы получите максимальную отдачу от CancellationToken, если все ваши операции будут асинхронными и могут быть отменены. Таким образом, отмена токена будет иметь немедленный эффект. Вам не придется ждать завершения Thread.Sleep или другого блокирующего вызова.

public static async Task DoWork(CancellationToken cancellationToken)
{
    while (true)
    {
        await Console.Out.WriteLineAsync("Working...");
        await Task.Delay(1500, cancellationToken);
    }
}

В этом примере токен передается только Task.Delay, поскольку WriteLineAsync нельзя отменить в .NET Framework (это в .NET Core).

OperationCanceledException будет поднято Task.Delay, когда токен будет отменен.

person Theodor Zoulias    schedule 08.10.2019

Обычно вы не хотите делать свою DoWork функцию async void — вместо этого сделайте ее async Task. Таким образом, вы можете видеть, когда он завершен (или отменен).

Вы также, вероятно, захотите использовать cancelToken.ThrowIfCancellationRequested(). Это выдает OperationCanceledException, который вы можете поймать.

public class Program
{
    public static CancellationTokenSource tokenSource;

    private static async Task Main(string[] args)
    {
        while (true)
        {
            await Work();
        }
    }

    public static async Task Work()
    {
        tokenSource = new CancellationTokenSource();
        Console.WriteLine("Press any key to START doing work");
        Console.ReadLine();
        Console.WriteLine("Press any key to STOP doing work");

        var task = DoWork(tokenSource.Token);

        Console.ReadLine();
        Console.WriteLine("Stopping...");
        tokenSource.Cancel();

        try
        {
            await task;
        }
        catch (OperationCanceledException)
        {
            // Task was cancelled
        }
    }

    public static async Task DoWork(CancellationToken cancelToken)
    {
        while (true)
        {
            Console.WriteLine("Working...");
            await Task.Run(() =>
            {
                Thread.Sleep(1500);
            });

            cancelToken.ThrowIfCancellationRequested();
        }
    }
}

Этот код основан на «асинхронном основном», который был представлен в C# 7. Если у вас его нет, вы можете написать свой метод Main следующим образом:

private static void Main(string[] args)
{
    while (true)
    {
        Work().Wait();
    }
}
person canton7    schedule 08.10.2019
comment
Почему просто используется свойство CancellationToken.IsCancellationRequested? А также почему в инфинитиве 2 петли? - person is_oz; 08.10.2019
comment
@is_oz структура цикла исходит из вопроса ОП - это не мой код. Для IsCancellationRequested и ThrowIfCancellationRequested это не имеет большого значения для этого игрушечного примера, но позволяет вызывающей стороне определить разницу между операцией, завершенной успешно, и операцией, которая была отменена. Поскольку OP играет с отменой, это полезно изучить. - person canton7; 08.10.2019
comment
@canton7 Я ценю ваши замечания, однако приведенный вами пример запускает метод DoWork() только тогда, когда я готов его отменить. Я пытаюсь создать демонстрационное приложение, в котором я могу нажать Enter, чтобы отменить его, когда захочу, а затем снова повторить процесс запуска работы и так далее... - person David Andrew Thorpe; 08.10.2019
comment
@DavidAndrewThorpe Он начинает работу точно в то же время, когда код в вашем вопросе запускает его, поэтому, боюсь, вам нужно уточнить свою жалобу. - person canton7; 08.10.2019
comment
@canton7 canton7 Кажется, у меня проблема с кодом, он не ожидает «ожидания задачи», поэтому Work () вызывается снова, прежде чем я вижу строку Work Canceled - person David Andrew Thorpe; 08.10.2019
comment
Здесь это работает нормально. Почему вы думаете, что он не ждет в await task? - person canton7; 08.10.2019
comment
Я попробовал код, и он не ждет. Я изменил «ожидание задачи» на «task.Wait ()» и перестал выдавать исключение, и оно работает по назначению. - person David Andrew Thorpe; 08.10.2019
comment
Чтение @canton7 означает, что он не будет ждать в заголовке «ожидающая задача» stackoverflow.com/questions/15149811/ - person David Andrew Thorpe; 08.10.2019
comment
task.Wait() также должен генерировать исключение, но это AggregateException, которое не будет перехвачено. - person canton7; 08.10.2019
comment
Что в этом вопросе заставляет вас думать, что await task не будет ждать выполнения задания? Это буквально весь смысл ключевого слова await. - person canton7; 08.10.2019
comment
@DavidAndrewThorpe Я обновил код, чтобы он принимал async вплоть до Main (хотя вам нужно будет скомпилировать как C # 7 или выше). Посмотрите, есть ли разница. - person canton7; 08.10.2019
comment
@canton7 Ответ с 206 голосами подразумевает, что ожидание задачи не будет ждать ее завершения. Опять же, я только что полностью скопировал ваш код и запустил его, и он работает, как вы сказали. Я сделал ошибку где-то в своем коде, поэтому извиняюсь!! И еще раз спасибо за ответ - person David Andrew Thorpe; 08.10.2019
comment
@DavidAndrewThorpe не блокирует поток до тех пор, пока Task не завершится, но останавливает выполнение этого метода. В этом вся суть async/await. - person canton7; 08.10.2019
comment
@canton7 Аааа, да, я неправильно понял - person David Andrew Thorpe; 08.10.2019