Как прервать выполнение асинхронного метода через некоторое время
Что проверяют
Понимаете ли вы, что в .NET асинхронный метод нельзя прервать принудительно, его можно только попросить остановиться через CancellationToken. Следом спрашивают, что делать с методом, который токен не принимает, и как совместить свой таймаут с токеном вызывающего кода.
Как отвечать
Базовая схема:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
try { await DoWorkAsync(cts.Token); }
catch (OperationCanceledException) when (cts.IsCancellationRequested) { /* таймаут */ }Детали, по которым видно опыт:
- токен до самого дна: его передают в
Task.Delay,HttpClient.SendAsync,ToListAsyncиз EF Core,ReadAsyncу потоков. Если внутри толькоThrowIfCancellationRequested()между шагами, отмена срабатывает с задержкой в целый шаг; - связанные токены:
CancellationTokenSource.CreateLinkedTokenSource(externalToken)плюсCancelAfter(timeout)дают отмену и по таймауту, и снаружи, например поHttpContext.RequestAbortedв ASP.NET Core; - исключения: отмена приходит как
OperationCanceledExceptionили его наследникTaskCanceledException, ловите базовый тип. Чтобы отличить свой таймаут от отмены вызывающим, проверяйте, чей токен сработал; - метод без токена:
task.WaitAsync(TimeSpan)из .NET 6 перестаёт ждать и бросаетTimeoutException, но сама операция продолжает работать в фоне. Честно назовите это ограничение; - готовые политики: в продакшене таймауты часто задают через Polly или
Microsoft.Extensions.Http.Resilience, а уHttpClientесть свойTimeout, по умолчанию 100 секунд.
CancellationTokenSource реализует IDisposable, и при CancelAfter и связанных токенах его важно освобождать, иначе остаются таймеры и регистрации.
Где спотыкаются
Проверяют токен в цикле, но не передают его в Task.Delay и сетевые вызовы. Долгий шаг досиживает до конца, и таймаут в 3 секунды превращается в 10.
Используют Task.WhenAny(work, Task.Delay(timeout)) и считают, что работа остановилась. Она продолжается и может, например, дописать данные уже после ответа клиенту.
Вспоминают Thread.Abort. В современном .NET он не поддерживается и бросает PlatformNotSupportedException.