Отмена. Часть 1: Обзор. Начало
Отмена — это тема, о которой возникает множество вопросов. И хотя документация Microsoft довольно хороша, в этой серии подробно рассмотрим эту тему.
Скоординированная отмена в .NET
Это означает, что одна часть кода запрашивает отмену, а другая часть кода отвечает на этот запрос. Мы часто говорим о том, что один код «отменяет» другой код, но на самом деле запрашивающий код просто вежливо уведомляет другой код о том, что он хотел бы его остановить, а отвечающий код может отреагировать на этот запрос на отмену любым способом. Отвечающий код может немедленно остановить то, что он делает, или продолжать работу до тех пор, пока не достигнет допустимой точки остановки, либо он может полностью игнорировать запрос на отмену.
Важный вывод: отвечающий код должен ответить на запрос отмены, чтобы отмена действительно что-то отменила.
Обычно возникает вопрос: как отменить неотменяемый код? Это сложный сценарий, рассмотрим его позже.
Токены отмены и «90% случаев»
В .NET токен отмены является «носителем» запроса на отмену. Запрашивающий код вызывает отмену на токене, а отвечающий код реагирует на это. Т.е. токен отмены — это то, как запрос отмены передается от запрашивающего кода к отвечающему коду.
Поэтому в 90% случаев добавьте параметр CancellationToken в метод, а затем передайте его любому API, который вы вызываете:
async Task DoAsync(
int data,
CancellationToken ct)
{
var myVal = await DoFirstAsync(data, ct);
await DoSecondAsync(myVal, ct);
}
Токен отмены может обозначать отмену любого типа: нажатие пользователем кнопки «Отмена»; клиент отключается от сервера; закрытие приложения; тайм-аут. Для вашего кода не должно иметь значения, почему он отменяется; просто его отменяют.
Каждый токен можно отменить только один раз; как только токен отменён, он отменён навсегда.
Контракт отмены: сигнатура метода
По соглашению параметр CancellationToken обычно является последним, если не присутствует параметр IProgress<T>. Обычно предоставляется перегрузка или значение по умолчанию, чтобы вызывающему коду не приходилось предоставлять CancellationToken, если его нет; значение CancellationToken по умолчанию - CancellationToken.None, т.е. токен, который никогда не будет отменён:
async Task DoAsync(int data) =>
DoAsync(data, CancellationToken.None);
async Task DoAsync(int data, CancellationToken ct)
{
…
}
// либо
async Task DoAsync(
int data, CancellationToken ct = default)
{
…
}
Некоторые сигнатуры методов принимают CancellationToken и значение тайм-аута как отдельные параметры. В основном это делается в BCL, чтобы обеспечить более эффективные методы p/Invoke, которые принимают параметры тайм-аута. Если не используете API p/Invoke, используйте только CancellationToken, который может представлять собой любой вид отмены.
Принимая параметр CancellationToken, метод неявно заявляет, что он может отреагировать на отмену. Технически это означает «может ответить», а не «должен ответить». В некоторых случаях (например, при реализации интерфейса) аргумент CancellationToken может игнорироваться. Таким образом, наличие параметра CancellationToken не обязательно означает, что код должен поддерживать отмену.
Окончание следует…
Источник: https://blog.stephencleary.com/2022/02/cancellation-1-overview.html