Как остановить асинхронную задачу?

Предположим, у меня есть List<Task>, который помогает мне управлять всеми задачами на основе индекса:

private List<Task> tasks = new List<Task>(new Task[3]);

При запуске приложения отображается меню, и пользователь может выбрать, какую задачу можно запустить, просто:

public void Menu()
{
    Console.WriteLine("1. Task");
    Console.WriteLine("2. Task");
    Console.WriteLine("3. Task");
    Console.WriteLine("0. Exit");

    ConsoleKeyInfo input;

    do
    {
        input = Console.ReadKey();
        Console.WriteLine();

        switch (input.Key)
        {
            case ConsoleKey.NumPad1:
                 StartTask(1);
                 break;
            case ConsoleKey.NumPad2:
                 StartTask(2);
                 break;
            case ConsoleKey.NumPad3:
                 StartTask(3);
                 break;
        }
     } while (input.Key != ConsoleKey.NumPad0);
}

внутри метода StartTask я передаю номер Task для выполнения, это логика:

public void StartTask(int instance)
{
    int index = instance - 1;

    //Task not yet instantiated, create new one
    if(tasks.ElementAtOrDefault(index) == null)
    {
        //Make a new cancellation token
        cts.Insert(index, new CancellationTokenSource());

        tasks.Insert(index, new Task(async () => 
        {
            await foo.DoWork(cts[index].Token);
        }, cts[index].Token));

        if(!tasks[index].Status.Equals(TaskStatus.Running))
        {
            tasks[index].Start();
        }
    }
}

Как видите, у меня также есть List<CancellationTokenSource> cts, который помогает мне завершить задачу. Метод Cancel помещен в другое меню, как указано выше.

Теперь проблема довольно ясна: я не могу этого сделать, потому что проверка выполняется перед операцией await для DoWork();, которая содержит это:

public class Foo
{
     private async Task DoWorkAsync(CancellationToken token)
     {
         while (!token.IsCancellationRequested)
         {
             string newData = DateTime.Now.ToLongTimeString();

             Console.WriteLine(newData);
              await Task.Delay(200);
         }
     }
}

по сути: как я могу остановить Task, который запускает loop с CancellationToken?

Токен, переданный в качестве параметра, не может изменить значение, если остановка выполняется позже начала. По сути, значение IsCancellationRequested изменяется только в контексте класса Task, а не в переданном аргументе.

Вам нужно переслать токен

Aluan Haddad 31.10.2018 12:57

@AluanHaddad, а если DoWorkAsync принадлежит к другому классу? Изменение переменной не произойдет в другом классе только в контексте задачи.

Spartaok 31.10.2018 12:58

Если StartTask не помечен как async, как можно await DoWorkAsync?

Fildor 31.10.2018 13:02

@Fildor Мне не нужно ждать, мне просто нужно запустить задачу и управлять задачей, остановив ее

Spartaok 31.10.2018 13:04

Я имел ввиду: нельзя использовать await. Но это в вашем примере кода.

Fildor 31.10.2018 13:05
1
5
534
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Передайте токен отмены в DoWorkAsync. И используйте его в цикле while как условие.

while(!token.IsCancellationRequested)
{ 
   await Task.Delay(200, token); 
}

«токен» должен быть передан в качестве параметра. Если честно, я думаю, что в вашем решении слишком много избыточного кода ...

Как отменить задачу с помощью источника токена отмены:

void CancelTask(int taskNumber)
{
   int index = taskNumber - 1;
   cts[index].Cancel();
}

Этот код установит флаг IsCancellationRequested для токена, который вы используете в цикле, в значение true.

Как я могу предотвратить избыточность? Обновлено: Я пробовал, и это не сработает. Я получаю значение true для IsCancellationRequested только в том классе, в котором я внес изменения, а не в классе методов. Это потому, что отмена производится позже начала цикла.

Spartaok 31.10.2018 13:10

Я вижу избыточность в методе StartTask. Это слишком сложно для запуска задачи. Кроме того, я не понимаю, как вы хотите отменить начатые вами задачи. Какой метод за это отвечает? Вы разбираете вводимые пользователем данные? Или вы хотите сделать это автоматически?

Eramir 31.10.2018 13:18

пожалуйста, проверьте мое обновление, вы увидите, что в моем случае ваше решение не работает

Spartaok 31.10.2018 13:23

@Spartaok, не могли бы вы объяснить, как вы хотите остановить свои задачи? В идеале задачи будут останавливаться сами по себе, когда они прекратят выполнение, в вашем случае, если у вас бесконечный цикл, вам нужен способ вручную вызвать метод CancellationTokenSource.Cancel

Eramir 31.10.2018 13:25

По сути, каждая задача, запущенная пользователем, будет запускать парсер для определенной части веб-сайта, я хочу иметь возможность управлять запуском и остановкой каждой задачи процесса очистки. Метод Cancel, как я уже сказал в вопросе, находится в другом меню

Spartaok 31.10.2018 13:27

Цикл не должен изменять значение класса token.IsCancellationRequested во время выполнения. Вам понадобится дополнительный код, чтобы вручную вызвать метод Cancel соответствующего CancellationTokenSource в массиве cts.

Eramir 31.10.2018 13:30

Позвольте нам продолжить обсуждение в чате.

Spartaok 31.10.2018 14:20

Другие вопросы по теме