2016-10-31 2 views
1

Почему этот тест не прошел?
Единственное различие между t1 и t2, насколько я могу сказать, что t1 является Task и t2 является Task<int>. Однако по какой-то причине t2 попадает в состояние Faulted, а не в Canceled. Почему поведение будет отличаться?Поведение задачи отличается от задачи <T>, когда OperationCanceledException выбрано

[Test] 
public void Test_foo() 
{ 
    var t1 = Task.Run(() => 
    { 
     throw new OperationCanceledException(); 
    }); 
    try 
    { 
     t1.Wait(); 
    } 
    catch (AggregateException e) 
    { 
     Assert.IsTrue(t1.IsCanceled); 
    } 

    var t2 = Task.Run(() => 
    { 
     throw new OperationCanceledException(); 
     return 1; 
    }); 
    try 
    { 
     t2.Wait(); 
    } 
    catch (AggregateException e) 
    { 
     Assert.IsTrue(t2.IsCanceled); // fails, it's Faulted 
    } 
} 

ответ

1

Основное различие между вашими задачами является перегрузка для Task.Run метода, который вы используете:

task1 создается с Task.Run Method (Func<Task>), а не task2 создается с Task.Run<TResult> Method (Func<TResult>). Это перегружает ли создать задачу с небольшой разницей битной:

  • для task1 свойство Result устанавливаются в System.Threading.Tasks.VoidTaskResult и CreationOptions установлен в None,
  • , а не task2CreationOptions установлен в DenyChildAttach, и результат представляет собой default(int), который составляет 0.

Когда вы ждете в task2, свойство Result не быть установлен в реальной стоимости, так как исключение. По MSDN:

Когда экземпляр задачи наблюдает в OperationCanceledException брошенного коды пользователя, он сравнивает маркер, за исключением к связанному с ним лексемами (тот, который был передан в API, который создал Task). Если они одинаковы и IsCancellationRequested свойства лексемы возвращает истинного, задача интерпретирует это как признание отмены и переходов в Canceled состояния. Если вы не используете метод Wait или WaitAll для ожидания задания, задача просто устанавливает свой статус на Canceled.

Если вы ждете на Задаче, переходящую в Canceled состояния, System.Threading.Tasks.TaskCanceledException исключения (завернутые в AggregateException исключения) выбрасываются. Примечание , что это исключение указывает на успешную отмену, а не на неисправную ситуацию . Поэтому задание Exception возвращает имущество null.

Если IsCancellationRequested свойства лексемы возвращает false или если маркер за исключением не соответствует Целевому маркеру, то OperationCanceledExceptionтрактуются как обычное исключение, вызывая задачу перехода к Faulted состояния.Также обратите внимание, что наличие других исключений также приведет к переходу Task к состоянию Faulted. Статус завершенной задачи можно получить в Status.

Итак, здесь мы можем найти причину такого поведения - исключение рассматривается как нормальное исключение из-за несоответствия токенов. Это странно, потому что токен definitely the same (я проверил, что в Debug хеш-код равен Equals метод и оператор double equals возвращает true), но сравнение все равно возвращает false. Таким образом, решение для Вашего случая является явное использование в cancellation tokens, что-то вроде этого (я добавил Thread.Sleep, чтобы избежать состояния гонки):

var t1TokenSource = new CancellationTokenSource(); 
var t1 = Task.Run(() => 
{ 
    Thread.Sleep(1000); 
    if (t1TokenSource.Token.IsCancellationRequested) 
    { 
     t1TokenSource.Token.ThrowIfCancellationRequested(); 
    } 
    //throw new TaskCanceledException(); 
}, t1TokenSource.Token); 
try 
{ 
    t1TokenSource.Cancel(); 
    t1.Wait(); 
} 
catch (AggregateException e) 
{ 
    Debug.Assert(t1.IsCanceled); 
} 

var t2TokenSource = new CancellationTokenSource(); 
var t2 = Task.Run(() => 
{ 
    Thread.Sleep(1000); 
    if (t2TokenSource.Token.IsCancellationRequested) 
    { 
     t2TokenSource.Token.ThrowIfCancellationRequested(); 
    } 
    //throw new TaskCanceledException(); 
    return 1; 
}, t2TokenSource.Token); 
try 
{ 
    t2TokenSource.Cancel(); 
    t2.Wait(); 
} 
catch (AggregateException e) 
{ 
    Debug.Assert(t2.IsCanceled); 
} 

Другая цитата из MSDN:

Вы может прекратить операцию, используя один из следующих вариантов:

  • Просто вернувшись от делегата. Во многих сценариях этого достаточно; однако экземпляр задачи, который отменяется таким образом, переходит в состояние TaskStatus.RanToCompletion, а не в состояние TaskStatus.Canceled.
  • Отбрасывая OperationCanceledException и передавая ему токен, по которому была запрошена отмена. Предпочтительный способ сделать это: , чтобы использовать метод ThrowIfCancellationRequested. Задача, которая отменена таким образом, переходит в состояние Canceled, которое вызывающий код может использовать для проверки того, что задача ответила на его запрос на отмену .

Как вы можете видеть, Предпочитаемый способ работает предсказуемо, прямой бросок исключения не делает. Также обратите внимание, что в случае использования task также создается с DenyChildAttach и не имеет свойства Result, поэтому есть некоторая разница в конструкторах, с которыми вы столкнулись.
Надеюсь, это поможет.