Подтвердить что ты не робот

Отмена задачи бросает исключение

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

Выход из следующей программы:

Исключение сбрасывания

[OperationCanceledException]

Отмена и возврат последнего вычисленного простого числа.

Я пытаюсь избежать каких-либо исключений при отмене. Как я могу это сделать?

void Main()
{
    var cancellationToken = new CancellationTokenSource();

    var task = new Task<int>(() => {
        return CalculatePrime(cancellationToken.Token, 10000);
    }, cancellationToken.Token);

    try
    {
        task.Start();
        Thread.Sleep(100);
        cancellationToken.Cancel();
        task.Wait(cancellationToken.Token);         
    }
    catch (Exception e)
    {
        Console.WriteLine("Dumping exception");
        e.Dump();
    }
}

int CalculatePrime(CancellationToken cancelToken, object digits)
{  
    int factor; 
    int lastPrime = 0;

    int c = (int)digits;

    for (int num = 2; num < c; num++)
    { 
        bool isprime = true;
        factor = 0; 

        if (cancelToken.IsCancellationRequested)
        {
            Console.WriteLine ("Cancelling and returning last calculated prime.");
            //cancelToken.ThrowIfCancellationRequested();
            return lastPrime;
        }

        // see if num is evenly divisible 
        for (int i = 2; i <= num/2; i++)
        { 
            if ((num % i) == 0)
            {             
                // num is evenly divisible -- not prime 
                isprime = false; 
                factor = i; 
            }
        } 

        if (isprime)
        {
            lastPrime = num;
        }
    }

    return lastPrime;
}
4b9b3361

Ответ 1

Вы явно бросаете исключение в этой строке:

cancelToken.ThrowIfCancellationRequested();

Если вы хотите изящно выйти из задачи, вам просто нужно избавиться от этой строки.

Обычно люди используют это как механизм управления, чтобы гарантировать, что текущая обработка будет прервана без потенциального запуска какого-либо дополнительного кода. Кроме того, нет необходимости проверять отмену при вызове ThrowIfCancellationRequested(), поскольку она функционально эквивалентна:

if (token.IsCancellationRequested) 
    throw new OperationCanceledException(token);

При использовании ThrowIfCancellationRequested() ваша задача может выглядеть примерно так:

int CalculatePrime(CancellationToken cancelToken, object digits) {
    try{
        while(true){
            cancelToken.ThrowIfCancellationRequested();

            //Long operation here...
        }
    }
    finally{
        //Do some cleanup
    }
}

Кроме того, Task.Wait(CancellationToken) выдаст исключение, если токен был отменен. Чтобы использовать этот метод, вам нужно будет обернуть ваш вызов Wait в блоке Try...Catch.

MSDN: как отменить задачу

Ответ 2

Я пытаюсь избежать каких-либо исключений при отмене.

Вы не должны этого делать.

Throwing OperationCanceledException - это идиоматический способ, которым "отмененный метод" выражается в TPL. Не боритесь с этим - просто ожидайте этого.

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

Обратите внимание, что в вашем примере есть два фрагмента кода, которые бросают исключение - одно внутри самой задачи:

cancelToken.ThrowIfCancellationRequested()

и тот, где вы ждете завершения задачи:

task.Wait(cancellationToken.Token);

Я не думаю, что вы действительно хотите передать токен отмены в вызов task.Wait, если честно... это позволяет другому коду отменить ваше ожидание. Учитывая, что вы знаете, что вы только что отменили этот токен, это бессмысленно - оно обязано выбросить исключение, действительно ли задача фактически заметила отмену или нет. Параметры:

  • Используйте другой токен отмены (чтобы другой код мог независимо отменить ваш ожидания)
  • Использовать тайм-аут
  • Подождите столько, сколько потребуется

Ответ 3

Некоторые из вышеприведенных ответов читаются так, как будто ThrowIfCancellationRequested() будет вариантом. В этом случае это не, потому что вы не получите результат последнего последнего. idiomatic way that "the method you called was cancelled" определяется для случаев, когда отмена означает выброс любых (промежуточных) результатов. Если ваше определение отмены - "стоп-вычисление и возврат последнего промежуточного результата", вы уже оставили этот путь.

Обсуждение преимуществ, особенно с точки зрения времени выполнения, также довольно вводит в заблуждение: Внедренный алгоритм засасывает во время выполнения. Даже очень оптимизированная отмена не принесет никакой пользы.

Самая простая оптимизация - это развернуть этот цикл и пропустить некоторые ненужные циклы:

for(i=2; i <= num/2; i++) { 
  if((num % i) == 0) { 
    // num is evenly divisible -- not prime 
    isprime = false; 
    factor = i; 
  }
} 

Вы можете

  • сохранить (num/2) -1 циклов для каждого четного числа, что составляет чуть меньше 50% (разворачивание),
  • save (num/2) -square_root_of (num) циклов для каждого простого (выберите оценку по математике с наименьшим простым множителем),
  • сэкономить, по крайней мере, так много для каждого нестандартного, ожидают гораздо большей экономии, например. num = 999 завершается с 1 циклом вместо 499 (разрыв, если ответ найден) и
  • сэкономить еще 50% циклов, что, конечно же, составляет 25% в целом (выберите шаг согласно математике простых чисел, разматывание обрабатывает специальный случай 2).

Это означает сохранение гарантированного минимума 75% (приблизительная оценка: 90%) циклов во внутреннем цикле, просто заменив его:

if ((num % 2) == 0) {
  isprime = false; 
  factor = 2;
} else {
  for(i=3; i <= (int)Math.sqrt(num); i+=2) { 
    if((num % i) == 0) { 
      // num is evenly divisible -- not prime 
      isprime = false; 
      factor = i;
      break;
    }
  }
} 

Есть намного более быстрые алгоритмы (которые я не буду обсуждать, потому что я достаточно далеко не по теме), но эта оптимизация довольно проста и по-прежнему доказывает мою точку зрения: Не беспокойтесь о микро-оптимизации времени выполнения, когда ваш алгоритм этот далеко не оптимален.

Ответ 4

Еще одно замечание о пользе использования ThrowIfCancellationRequested, а не IsCancellationRequested: я обнаружил, что, когда вам нужно использовать ContinueWith с опцией продолжения TaskContinuationOptions.OnlyOnCanceled, IsCancellationRequested не вызовет условный ContinueWith стрелять. ThrowIfCancellationRequested, однако, установит условие "Отменено" задачи, в результате чего загорается ContinueWith.

Примечание. Это справедливо только в том случае, если задача уже запущена, а не при запуске задачи. Вот почему я добавил Thread.Sleep() между началом и отменой.

CancellationTokenSource cts = new CancellationTokenSource();

Task task1 = new Task(() => {
    while(true){
        if(cts.Token.IsCancellationRequested)
            break;
    }
}, cts.Token);
task1.ContinueWith((ant) => {
    // Perform task1 post-cancellation logic.
    // This will NOT fire when calling cst.Cancel().
}

Task task2 = new Task(() => {
    while(true){
        cts.Token.ThrowIfCancellationRequested();
    }
}, cts.Token);
task2.ContinueWith((ant) => {
    // Perform task2 post-cancellation logic.
    // This will fire when calling cst.Cancel().
}

task1.Start();
task2.Start();
Thread.Sleep(3000);
cts.Cancel();

Ответ 5

У вас есть две вещи для прослушивания токена: метод вычисления простого числа, а также экземпляр Task с именем task. Метод Calculate Prime должен возвращаться изящно, но задача отменяется, пока она еще выполняется, поэтому она выдает. Когда вы создаете задание, не удосуживайтесь дать ему токен.