C# - Что не так с моим CancellationToken? - PullRequest
0 голосов
/ 16 апреля 2020

Пожалуйста, посмотрите на этот код, работающий на. Net Core 2.0:

var src = new CancellationTokenSource(5000);
var token = src.Token;
var responseTask = await Task.Factory.StartNew(async () =>
{
   //Uncomment bellow to reproduce locally
   //await Task.Delay(60000);

   return await BadSDK.OperationThatDoesNotReceiveCancellationToken();//takes around 1 min
}, token);

var response = await responseTask;

Моя проблема здесь в том, что await всегда ожидает очень длинный вызов SDK, вместо ожидания 5se c.

Что я делаю не так? Где мое понимание неверно?

Edit1: этот код ведет себя так, как ожидалось:

var src = new CancellationTokenSource(5000);
var token = src.Token;
var responseTask = Task.Factory.StartNew(() =>
{
    var task = BadSDK.OperationThatDoesNotReceiveCancellationToken();
    task.Wait(token);
    cancellationToken.ThrowIfCancellationRequested();
    return task.Result;
}, token);

означает, что через 5 секунд выдается исключение

Ответы [ 3 ]

2 голосов
/ 16 апреля 2020

Проблема состоит в том, что шаблон токена отмены ожидает, что задача проверит токен и завершит работу или выдаст ошибку, когда токен истек. Хорошо написанные задачи будут периодически проверять, отменена ли отмена, и затем задача может выполнить любую необходимую очистку и корректно вернуться или выдать ошибку.

Как вы продемонстрировали, BadSDK.OperationThatDoesNotReceiveCancellationToken не принимает CancellationToken и, следовательно, не будет предпринимать никаких действий на основе токена. Неважно, если токен автоматически запрашивает отмену через тайм-аут, или если запрос выдан в другом вопросе. Простой факт заключается в том, что BadSDK.OperationThatDoesNotReceiveCancellationToken просто не проверяет его.

В вашем Edit1 CancellationToken передается в Wait, который действительно следит за токеном и завершается при запросе отмены. Это не означает, однако, что задача была убита или остановлена, она только перестала ждать ее. В зависимости от того, что вы намерены, это может или не может делать то, что вы хотите. Хотя он вернется через 5 секунд, задача все равно будет продолжаться. Вы могли бы даже ждать этого снова. Затем может быть возможно убить задачу, но на практике это может быть очень плохой задачей (см. Можно ли отменить задачу, например, прерывание потока (метод Thread.Abort)? )

1 голос
/ 16 апреля 2020

Перегрузка StartNew, которую вы использовали, является источником бесконечной путаницы. Вдвойне, так как его тип на самом деле StartNew<Task<T>> (вложенная задача), а не StartNew<T>.

Токен сам по себе ничего не делает. Некоторый код где-то должен проверить токен и выдать исключение для выхода из Задачи.

Официальная документация следует этому шаблону:

    var tokenSource = new CancellationTokenSource();
    var ct = tokenSource.Token;

    var task = Task.Run(() =>
    {
        while (...)
        {
            if (ct.IsCancellationRequested) 
            {
                // cleanup your resources before throwing

                ct.ThrowIfCancellationRequested();
            }
        }
    }, ct); // Pass same token to Task.Run

Но если вы все равно проверяете токен и, возможно, выбрасываете исключение, зачем вам сначала передавать токен, и затем использовать такой же токен внутри замыкания?

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

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

PS

Если вы включены. Net Core или 4.5+, Task.Run предпочтительнее заводского подхода.

0 голосов
/ 16 апреля 2020

Вы передаете задачу токену отмены token, но не указываете, что делать с токеном в методе async.

Возможно, вы захотите добавить token.ThrowIfCancellationRequested(); в методе, возможно, обусловлено token.IsCancellationRequested. Таким образом, задача будет отменена, если вызывается src .Cancel().

...