Отмена. Часть 4: Проверка отмены
В большинстве случаев получаемый токен отмены просто передаётся на более низкий уровень. Но если нужен отменяемый код на самом низком уровне, есть несколько вариантов.
Обычный шаблон — периодически вызывать ThrowIfCancellationRequested:
void DoSomething(CancellationToken ct)
{
while (!done)
{
ct.ThrowIfCancellationRequested();
Thread.Sleep(200); // некоторая работа
}
}
В коде выше проверяется токен отмены до начала работы, что является хорошей практикой. Возможно, токен уже отменён к моменту начала выполнения вашей операции.
ThrowIfCancellationRequested проверит, запрошена ли отмена, и если да, то выдаст OperationCanceledException. Ваш код должен просто позволить этому исключению распространиться из метода.
Главный вопрос — как часто проверять. На него нет хорошего ответа; в идеале - несколько раз в секунду, но в общем случае - просто поместите его в лучшее место(а) и запустите несколько тестов, чтобы увидеть, достаточно ли отзывчива отмена.
Как не делать проверку
Существует антишаблон пулинга на предмет отмены в бесконечном цикле:
void DoSomethingForever(CancellationToken ct)
{
// плохой код, не делайте так
while (!ct.IsCancellationRequested)
Thread.Sleep(200); // некоторая работа
}
Код будет периодически проверять токен отмены; и при отмене происходит выход из метода. Этот метод не удовлетворяет контракту отмены, требующему выбрасывать исключение при отмене. Т.е. вызывающий код не может знать, был ли метод выполнен до конца или был отменён. Правильное решение — использовать ThrowIfCancellationRequested, даже для бесконечных циклов (как в предыдущем примере).
Когда проверять
Проверка - подходящий вариант для наблюдения за отменой, если код синхронный, например, код вычислительной операции. Но отмена в .NET применима как к синхронному коду, так и к асинхронному. Фактически, параллельные циклы и PLINQ имеют встроенную поддержку отмены: ParallelOptions.CancellationToken для Parallel и WithCancellation для PLINQ.
Уместно вводить ThrowIfCancellationRequested в асинхронный код, если вы не уверены, будут ли другие методы учитывать токен отмены. Помните, что метод, принимающий CancellationToken может учитывать отмену, но может и проигнорировать его. Поэтому в вашем коде можно добавить проверки отмены между «шагами»:
async Task DoComplexWorkAsync(CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
await DoStep1Async(ct);
ct.ThrowIfCancellationRequested();
await DoStep2Async(ct);
}
Хотя вы можете разбросать вызовы ThrowIfCancellationRequested по всему коду, как в примере выше, лучше это делать, только когда тестирование показывает, что код не учитывает отмену. Т.е. предполагайте, что DoStep1Async и DoStep2Async будут учитывать отмену, пока тестирование не докажет обратное.
Также уместно использовать ThrowIfCancellationRequested в определённых точках, где ваш код собирается сделать что-то затратное. Простое добавление туда проверки отмены означает, что вашему коду не придётся выполнять затратную работу, если он всё равно будет отменён.
Источник: https://blog.stephencleary.com/2022/03/cancellation-3-detecting-cancellation.html