День 2241. #ЗаметкиНаПолях
Выполнение Очистки в BackgroundService
Есть что-то немного странное в шаблоне Worker Service в .NET.
Создадим новый проект Worker Service с помощью:
dotnet new worker -n MyWorker
Класс Worker использует BackgroundService в качестве базового класса. Worker имеет метод ExecuteAsync(…), который вызывается при запуске сервиса и принимает CancellationToken. В ExecuteAsync(…) есть цикл while, который проверяет, отменён ли CancellationToken. Если да, цикл завершается, и всё, что находится за пределами цикла, должно быть выполнено. В цикле есть вызов Task.Delay(1000, stopToken), который задерживается на 1 секунду и также принимает токен отмены.
Если Worker делает мало работы, задержка во время каждой итерации цикла велика по сравнению со временем, которое занимает фактическая работа. И отмена, скорее всего, произойдет во время периода задержки. Если CancellationToken отменяется во время задержки, Task.Delay(…) должен выбросить TaskCanceledException.
Но этого не происходит. Метод ExecuteAsync(…) прекращает работу, но исключение не выбрасывается. Код за пределами цикла while не выполняется. Это означает, что любой код очистки, который я хочу запустить при отмене BackgroundService, скорее всего, не будет выполнен, т.к. отмена обычно происходит во время задержки. Это странно.
Если запустить аналогичное консольное приложение, Task.Delay(…) выбросит TaskCanceledException, и мы получим исключение.
var cts = new CancellationTokenSource(3000);
var stopToken = cts.Token;
int count = 0;
while (!stopToken.IsCancellationRequested)
{
Console.WriteLine($"{++count} - {DateTime.Now:HH:mm:ss}");
await Task.Delay(1000, stopToken);
}
Console.WriteLine("Exiting");
Получим:
1 - 01:23:45
2 - 01:23:46
3 - 01:23:47
Unhandled exception. System.Threading.Tasks.TaskCanceledException: A task was canceled.
…
Но фоновый процесс не выбрасывает исключения!
Решение
Обернём Task.Delay(1000, stopsToken) в try…catch, перехватывая TaskCanceledException:
protected override async Task
ExecuteAsync(CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
Console.WriteLine($"До: {DateTime.Now:HH:mm:ss}");
try
{
await Task.Delay(1000, stopToken);
}
catch (TaskCanceledException)
{
Console.WriteLine($"Отмена в Task.Delay.");
break;
}
Console.WriteLine($"После: {DateTime.Now:HH:mm:ss}");
}
Console.WriteLine("Очистка");
}
Если вы используете Task.Delay(1000) без токена отмены, код дождётся окончания задержки, прежде чем выходить из while, но любой код после выполнится.
Также можно попробовать:
await Task.Delay(1000, stopToken)
.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
// или
await Task.Delay(1000, stopToken)
.ContinueWith(t => { return t.Exception == default;});
Они тоже не прервут работу и выполнят код после задержки.
Источник: https://nodogmablog.bryanhogan.net/2025/02/doing-some-cleanup-in-a-canceled-background-service/
>>Click here to continue<<