Отмена задачи вызывает исключение - PullRequest
52 голосов
/ 08 сентября 2011

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

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

Сбросисключение

[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;
}

Ответы [ 4 ]

86 голосов
/ 08 сентября 2011

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

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

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

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

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

cancelToken.ThrowIfCancellationRequested()

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

task.Wait(cancellationToken.Token);

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

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

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

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) вызовет исключение, если токен был отменен.Чтобы использовать этот метод, вам нужно будет обернуть ваш запрос ожидания в блок Try...Catch.

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

8 голосов
/ 24 сентября 2013

Некоторые из приведенных выше ответов читаются как вариант 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; 
  }
} 

Вы можете

  • save (num / 2) -1 циклов для каждого четного числа, что в целом составляет чуть менее 50% (развертывание),
  • циклы сохранения (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;
    }
  }
} 

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

7 голосов
/ 01 марта 2013

Еще одно замечание о преимуществе использования 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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...