Есть ли способ отменить выполнение метода в отмененной задаче?

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

Сейчас я не знаю, как остановить выполнение метода вместе с задачей.

Ниже представлен цикл, который запускает задачи. Каждая отдельная задача выполняет метод ParseHorseData, который также запускает несколько других методов. Их выполнение занимает иногда много времени.

После отмены задачи, до завершения await Task.WhenAll(tasks);, проходит много времени.

Итак, как и в вопросе, есть ли способ отменить выполнение метода в отмененной задаче?

List<Task> tasks = new List<Task>();
int loopCounter = 0;
int taskCounter = 0;

//for all races in the file
for (int i = 0; i < _allRaces.Count; i ++)
{
    int j = i;

    if (TaskCancellation == true)
    {
        break;
    }

    Task task = Task.Run(async () =>
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            loopCounter++;

            ProgressBarTick("Requesting historic data", loopCounter, _allRaces.Count, 0);

            //if the race is from 2018
            if (_allRaces[j].RaceDate.Year == 2018)
            {
                Category = _allRaces[j].RaceCategory;
                Distance = _allRaces[j].RaceDistance.ToString();

                //for all horses in the race
                for (int h = 0; h < _allRaces[j].HorseList.Count; h++)
                {
                    HorseDataWrapper horse = new HorseDataWrapper();


                    //TIME CONSUMING
                    horse = ParseHorseData(_allRaces[j].HorseList[h], _allRaces[j].RaceDate);
                    _allRaces[j].HorseList[h] = horse;
                }
            }

            taskCounter++;

            if (loopCounter >= _allRaces.Count)
            {
                ProgressBarTick("Testing on historic data", taskCounter, _allRaces.Count, 0);
            }
        }
    }, _tokenSource.Token);

    tasks.Add(task);
}

try
{
    await Task.WhenAll(tasks);
}
catch (TaskCanceledException)
{
    //
}
finally
{
    _tokenSource.Dispose();
}

person bakunet    schedule 17.05.2019    source источник
comment
Вы не показали нам трудоемкий метод... Вы должны передать в него свой токен отмены, периодически проверять его и выдавать исключение/прервать, если он отменен. Или, по крайней мере, проверьте это внутри этого внутреннего цикла, прежде чем выполнять следующую итерацию цикла. На данный момент у вашего кода есть шанс отменить только после того, как весь цикл будет запущен.   -  person Milney    schedule 17.05.2019
comment
Что ж, добавьте параметр CancellationToken as в парсер. Таким образом, вы также можете проверить ЭТО и остановить выполнение/возврат.   -  person k1ll3r8e    schedule 17.05.2019
comment
Спасибо, ребята, я нашел еще один вариант решения проблемы, но вы оба вдохновили меня. У меня уже было условие if (TaskCancellation == true) в цикле for. Циклы в методе, вызываемом задачей, занимают большую часть времени выполнения. После нажатия кнопки отмены btn срабатывает не только CancellationToken, но и вместо того, чтобы передавать параметр ct методам, также в их циклах я использую if (TaskCancellation == true) для break их. Еще раз спасибо.   -  person bakunet    schedule 17.05.2019


Ответы (1)


есть ли способ отменить выполнение метода в отмененной задаче?

Все отмены являются кооперативными. Выполняемый метод должен передать CancellationToken в методы, которые он вызывает, и либо:

  1. Периодически запрашивать отмену с помощью ThrowIfCancellationRequested. Этот подход больше подходит для циклов, привязанных к процессору.
  2. Выполните действие по отмене с помощью Register. Этот подход больше подходит для взаимодействия с системами отмены, не основанными на CancellationToken.

В этом случае кажется, что опрос уместен. Я настоятельно рекомендую проводить опрос через ThrowIfCancellationRequested, а не через IsCancellationRequested, потому что при отмене задачи она должна выдавать OperationCanceledException при awaited. Вот как вызывающий код узнает, что он был отменен.

Пример:

Task task = Task.Run(async () =>
{
  while (true)
  {
    _cancellationToken.ThrowIfCancellationRequested();
    ...
       //for all horses in the race
       for (int h = 0; h < _allRaces[j].HorseList.Count; h++)
       {
          _cancellationToken.ThrowIfCancellationRequested();
          HorseDataWrapper horse = new HorseDataWrapper();
          horse = ParseHorseData(_allRaces[j].HorseList[h], _allRaces[j].RaceDate);
          _allRaces[j].HorseList[h] = horse;
       }
    ...
  }
});
person Stephen Cleary    schedule 17.05.2019
comment
В моем примере я также ловлю A task was canceled.. Должно быть мало? А по вашему шаблону не должно быть if (_cancellationToken.IsCancellationRequested) _cancellationToken.ThrowIfCancellationRequested(); ? - person bakunet; 17.05.2019
comment
Да, вы должны поймать OperationCanceledException при отмене задачи. Нет, ThrowIfCancellationRequested срабатывает только в том случае, если запрашивается отмена, поэтому дополнительная проверка не требуется. - person Stephen Cleary; 18.05.2019
comment
Хорошо, это было проверено, и это сработало. С вашей идеей прямо сейчас он бросает exceprion для отмененных задач. Раньше это вызывало исключение только для токена. Спасибо! - person bakunet; 20.05.2019
comment
Это старый вопрос, но могу я задать вам еще один вопрос? В моем случае (из вопроса) я использую Task task = Task.Run(async () => в цикле for для создания множества параллельных задач. Это правильный подход? - person bakunet; 17.06.2019
comment
@bakunet: Если бы это было возможно, я бы отделил параллельную работу от асинхронной. TPL Dataflow хорош для этого, или, если вам нужна более простая очередь производителя/потребителя, Channels — лучший современный подход. - person Stephen Cleary; 17.06.2019