Использование Токенов Отмены
Сегодня посмотрим, почему стоит использовать токены отмены в вашем API. Дело не только в вашем коде.
Представьте, что у нас есть длительный SQL-запрос, например:
-- MSSQL
SELECT COUNT_BIG(*)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
CROSS JOIN sys.all_objects c
CROSS JOIN sys.all_objects d
CROSS JOIN sys.all_objects e;
Конечно, это всего лишь «бесполезный» демонстрационный код, но идея в том, что он выполняется долго и может значительно нагружать процессор, память и ввод-вывод. Если это часть вашего (REST) API и HTTP-запрос отменяется, SQL-запрос всё равно продолжит выполняться.
Вы можете легко проверить это с помощью:
SELECT * FROM sys.dm_exec_requests WHERE status = 'running';
И вы увидите, что этот запрос выполняется, даже если вызов REST прерывается. При завершении консольного приложения этого не произойдёт, т.к. это приведет к прерыванию соединения с БД и транзакции.
Теперь, если мы предоставим токен отмены:
using var cts =
new CancellationTokenSource(TimeSpan.FromSeconds(2));
try
{
await dbContext.Database.ExecuteSqlAsync(
$"""
SELECT COUNT_BIG(*)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
CROSS JOIN sys.all_objects c
CROSS JOIN sys.all_objects d
CROSS JOIN sys.all_objects e;
""", cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Запрос отменён!");
}
Теперь, если запустить тот же запрос к sys.dm_exec_requests, вы увидите, что после отмены, сервер также прерывает и SQL-запрос.
Так ваш код не только лучше масштабируется, поскольку может освобождать ненужные ресурсы, но и, если внешняя система также поддерживает это, она также может экономить ресурсы.
В этом примере это был SQL-сервер, который реагировал на отмену. Но если вы также передадите токен отмены в HttpClient, это прервёт запрос, и другая сторона может отреагировать таким же образом. См. Отмена Операции при Отмене HTTP Запроса.
Не факт, что это поддерживают все технологии. Похоже, что, например, провайдер для Postgresql это поддерживает. Так что технически это возможно, но опыт может отличаться. В любом случае, добавление токена — хорошая практика, поскольку поставщики могут улучшить поддержку в будущем.
Всегда ли нужно предоставлять токен отмены?
Если вы видите в вопросе слова «всегда» или «никогда», ответ, скорее всего, «нет». Конечно, общее правило — добавлять токен отмены к вызовам, которые его поддерживают, но давайте посмотрим на следующий код:
var user = new User { Name = dto.Name };
db.Users.Add(user);
await _db.SaveChangesAsync(ct);
var address =
new Address { UserId = user.Id, City = dto.City };
db.Addresses.Add(address);
await _db.SaveChangesAsync(ct);
var role =
new Role { UserId = user.Id, Name = "Member" };
db.Roles.Add(role);
await _db.SaveChangesAsync(ct);Представьте, что пользователь отменяет запрос, во время сохранения адреса. Пользователь добавляется, а адрес и роль — нет. Теперь вы находитесь в несогласованном состоянии. Конечно, этот пример надуманный. Простое решение — вызвать SaveChangesAsync только один раз в конце или использовать транзакцию, которая фиксируется в конце один раз. Но иногда у вас есть сторонний код, который также хранит информацию где-то, где вы не можете её контролировать. Поэтому нужно убедиться, что вы не столкнётесь с несогласованными состояниями из-за того, что кто-то прервал запрос!
Источник: https://steven-giesel.com/blogPost/080baaef-27d4-4d98-b0a8-9c3ab96c335e/use-cancellationtokens