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

Почему возникает TaskCanceledException?

У меня есть следующий тестовый код:

void Button_Click(object sender, RoutedEventArgs e)
{
    var source = new CancellationTokenSource();

    var tsk1 = new Task(() => Thread1(source.Token), source.Token);
    var tsk2 = new Task(() => Thread2(source.Token), source.Token);

    tsk1.Start();
    tsk2.Start();

    source.Cancel();

    try
    {
        Task.WaitAll(new[] {tsk1, tsk2});
    }
    catch (Exception ex)
    {
        // here exception is caught
    }
}

void Thread1(CancellationToken token)
{
    Thread.Sleep(2000);

    // If the following line is enabled, the result is the same.
    // token.ThrowIfCancellationRequested();
}

void Thread2(CancellationToken token)
{
    Thread.Sleep(3000);
}

В методах потоков я не делаю никаких исключений, но получаю TaskCanceledException в try-catch блок внешнего кода, который запускает задачи. Почему это происходит и какова цель token.ThrowIfCancellationRequested(); в этом случае. Я считаю, что исключение должно быть вызвано, если я вызову token.ThrowIfCancellationRequested(); в методе потоков.

4b9b3361

Ответ 1

Я считаю, что это ожидаемое поведение, потому что вы работаете с изменением состояния гонки.

От Как отменить задачу и ее детей:

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

и Отмена задачи:

Вы можете завершить операцию с помощью [...], просто возвращаясь из делегата. Во многих сценариях этого достаточно; однако экземпляр задачи, который "отменяется" таким образом, переходит в состояние RanToCompletion, а не в состояние Canceled.

Моя образованная догадка заключается в том, что, когда вы вызываете .Start() в своих двух задачах, возможно, что один (или оба они) на самом деле не запускались, прежде чем вы набрали .Cancel() на CancellationTokenSource. Бьюсь об заклад, если вы ставите хотя бы три секунды ожидания между началом заданий и отменой, это не вызовет исключения. Кроме того, вы можете проверить свойство .Status для обеих задач. Если я прав, свойство .Status должно читать TaskStatus.Canceled по крайней мере для одного из них при вызове исключения.

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