TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.74K subscribers
Post #3278 1.69K
День 2736. #ЗаметкиНаПолях #Cancellation
Токен Отмены: Ошибки, Которые Незаметно Приводят к Проблемам. Продолжение

Начало

Ошибка №3: Игнорирование OperationCanceledException
Отмена в .NET работает путём генерации исключений. Когда срабатывает токен, следующая ожидаемая операция генерирует исключение OperationCanceledException (или его подкласс TaskCanceledException). Блок catch, перехватывающий все исключения, перехватит и это — и теперь ваши логи полны «сбоев», которые на самом деле являются просто закрытием вкладок пользователями:
try
{
await service.BuildReportAsync(id, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Генерация отчёта не удалась");
throw;
}

Решение – ожидать отмену:
try
{
await service.BuildReportAsync(id, ct);
}
catch (OperationCanceledException)
when (ct.IsCancellationRequested)
{
_logger.LogDebug("Генерация отчёта №{Id} отменена клиентом", id);
throw; // позволяем ASP.NET Core перехватить это и вернуть правильный ответ
}
catch (Exception ex)
{
_logger.LogError(ex, "Генерация отчёта не удалась");
throw;
}

ASP.NET Core умеет обрабатывать необработанное OperationCanceledException, связанное с RequestAborted — оно завершает запрос без записи кода 500.

Ошибка №4: Предположение, что фоновый сервис получает токен запроса
Эта ошибка сбивает с толку, потому что выглядит как противоположная проблема. У фонового сервиса есть собственный токен отмены, и он не имеет отношения к HTTP-запросу. Он срабатывает только при завершении работы хоста:
public class OutboxProcessor : BackgroundService
{
private readonly IServiceProvider _services;

public OutboxProcessor(IServiceProvider services)
=> _services = services;

protected override async Task ExecuteAsync(
CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
using var scope = _services.CreateScope();
var db = scope.ServiceProvider
.GetRequiredService<AppDbContext>();

// stopToken здесь означает «приложение закрывается»,
// а не «эта часть работы отменена кем-то»
await ProcessAsync(db, stopToken);
await Task.Delay(TimeSpan.FromSeconds(5), stopToken);
}
}
}

Если вы запускаете фоновые процессы внутри обработчика запросов и передаёте в него RequestAborted, вы создаёте ошибку: фоновые процессы прекращаются в тот же момент, когда отправляется HTTP-ответ (потому что RequestAborted также срабатывает в этих случаях при некоторых конфигурациях хоста) или когда клиент отключается. Фоновым процессам, которые должны продолжаться после завершения запроса, нужен собственный токен — обычно IHostApplicationLifetime.ApplicationStopping, а не токен запроса:
app.MapPost("/reports/{id}/export", async (
int id,
ReportService svc,
IHostApplicationLifetime lt) =>
{
// НЕ RequestAborted – сервис должен продолжить
// работу, даже когда ответ клиенту отправлен
_ = svc.GenerateAsync(id, lt.ApplicationStopping);
return Results.Accepted();
});

Важное различие: RequestAborted означает «тот, кто вызывал этот запрос, отключился». ApplicationStopping означает «процесс завершается». Использование одного значения вместо другого либо приводит к преждевременной отмене работы, либо к лишней работе, которая должна была быть отменена вместе с запросом.

Окончание следует…

Источник:
https://thecodeman.net/posts/cancellation-tokens-in-aspnet-core-mistakes
  • 👍 10
More from @netdeveloperdiary
  1. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  2. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  3. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  4. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  5. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 21, 2026🔍Тестовое собеседование с Senior C# разработчиком уже завтра 22 сентября(уже завтра!) в 1…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →