ExecuteAsync, MyBackgroundServicesApp.slnx
https://giga.chat/link/gcsqkVfqAND:\Projects\VS02\2606\Services\Alice\MyBackgroundServicesApp\MyBackgroundServicesApp.slnx
D:\Projects\VS02\2606\Services\Alice\MyBackgroundServicesApp\MyBackgroundServicesApp\MyBackgroundServicesApp.csproj
------------------------------------------------------------------------------------------------------------
// Services/Service1.cs class Service1 : BackgroundService { private readonly IMessageBus _bus; private int _counter = 1; private readonly ILogger<Service1> _logger; public Service1(IMessageBus bus, ILogger<Service1> logger) { _bus = bus; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Service1 started sending commands..."); // Цикл продолжается, пока не получен сигнал об остановке от хоста. while (!stoppingToken.IsCancellationRequested) { // Создаем команду. Это легкая операция, ее можно делать всегда. var command = new SomeCommand(_counter, $"Data from Service1 - {DateTime.UtcNow}"); // Создаем локальный токен для операции отправки. // Он будет отменен, если либо придет общий сигнал остановки, // либо сам метод ExecuteAsync будет завершен. using var sendCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); try { // Отправляем команду. Если операция займет много времени, // ее можно будет отменить по sendCts.Token. await _bus.SendAsync(command, sendCts.Token); _counter++; // Задержка на 5 секунд. Токен НЕ передаем! // Это ключевой момент: мы даем циклу завершиться естественно. // Если придет сигнал остановки, Task.Delay просто "проснется" // и цикл проверит условие while на следующей итерации. await Task.Delay(TimeSpan.FromSeconds(5)); } catch (OperationCanceledException) when (sendCts.IsCancellationRequested) { // Эта секция сработает, если сама операция SendAsync была отменена. // Например, если шина сообщений поддерживает отмену и мы ее запросили. // В этом случае мы просто переходим к следующей итерации или выходим из цикла. _logger.LogWarning("Sending of command {CommandId} was cancelled.", command.Id); } catch (Exception ex) { // Логируем любые другие ошибки, но не даем им остановить сервис. _logger.LogError(ex, "Error occurred while sending command {CommandId}.", command.Id); } } // Этот лог будет выведен, когда цикл while завершится, // то есть после получения сигнала stoppingToken.IsCancellationRequested = true // и завершения последней итерации цикла. _logger.LogInformation("Service1 is shutting down gracefully..."); } }
-------------------------------------------------------------------------// Services/Service2.cs class Service2 : BackgroundService { private readonly IMessageBus _bus; private int _eventCounter = 1; private readonly ILogger<Service2> _logger; public Service2(IMessageBus bus, ILogger<Service2> logger) { _bus = bus; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Service2 started sending events..."); // Цикл работает, пока не получен сигнал об остановке от хоста. while (!stoppingToken.IsCancellationRequested) { // Создаем событие. var eventMsg = new SomeEvent( $"Service2Event-{_eventCounter}", DateTime.UtcNow ); // Создаем локальный токен для операции публикации. // Это хорошая практика для управления отменой конкретной операции. using var publishCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); try { // Публикуем событие. Токен передаем, чтобы операция могла быть отменена, // если она поддерживает отмену. await _bus.PublishAsync(eventMsg, publishCts.Token); _eventCounter++; // Ключевое изменение: убираем stoppingToken из Task.Delay. // Это позволяет задержке завершиться, давая сервису время на "плавную" остановку. await Task.Delay(TimeSpan.FromSeconds(7)); } catch (OperationCanceledException) when (publishCts.IsCancellationRequested) { // Логируем, если была отменена сама операция публикации. _logger.LogWarning("Publishing of event {EventId} was cancelled.", eventMsg); } catch (Exception ex) { // Логируем любые другие ошибки, чтобы сервис не "падал". _logger.LogError(ex, "Error occurred while publishing event {EventId}.", eventMsg); } } // Этот лог будет выведен после того, как цикл while завершится, // что произойдет после получения сигнала на остановку и завершения текущей итерации. _logger.LogInformation("Service2 is shutting down gracefully..."); } }
------------------------------------------------------------------------------------// Services/Service3.cs class Service3 : BackgroundService { private readonly IMessageBus _bus; private int _commandCounter = 1; private readonly ILogger<Service3> _logger; public Service3(IMessageBus bus, ILogger<Service3> logger) { _bus = bus; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Service3 started sending commands..."); while (!stoppingToken.IsCancellationRequested) { var command = new AnotherCommand( Guid.NewGuid(), DateTime.UtcNow ); // Используем локальный токен для операции отправки using var sendCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); try { // Отправка команды с возможностью отмены await _bus.SendAsync(command, sendCts.Token); _commandCounter++; // КЛЮЧЕВОЕ ИЗМЕНЕНИЕ: Убираем stoppingToken из Task.Delay. // Это позволяет задержке завершиться, давая сервису время на "плавную" остановку. await Task.Delay(TimeSpan.FromSeconds(10)); } catch (OperationCanceledException) when (sendCts.IsCancellationRequested) { // Логируем, если была отменена сама операция отправки. _logger.LogWarning("Sending of command {CommandId} was cancelled.", command); } catch (Exception ex) { // Логируем непредвиденные ошибки, чтобы сервис не "падал". _logger.LogError(ex, "Error occurred while sending command {CommandId}.", command); } } // Этот лог будет выведен после того, как цикл while завершится, // что произойдет после получения сигнала на остановку и завершения текущей итерации. _logger.LogInformation("Service3 is shutting down gracefully..."); } }
-------------------------------------------------------------------------------Прокомментируй мне загадочный фрагмент в этом коде try { // Отправляем команду. Если операция займет много времени, // ее можно будет отменить по sendCts.Token. await _bus.SendAsync(command, sendCts.Token); _counter++;
// Задержка на 5 секунд. Токен НЕ передаем! // Это ключевой момент: мы даем циклу завершиться естественно. // Если придет сигнал остановки, Task.Delay просто "проснется" // и цикл проверит условие while на следующей итерации. await Task.Delay(TimeSpan.FromSeconds(5));} catch (OperationCanceledException) when (sendCts.IsCancellationRequested) { // Эта секция сработает, если сама операция SendAsync была отменена. // Например, если шина сообщений поддерживает отмену и мы ее запросили. // В этом случае мы просто переходим к следующей итерации или выходим из цикла. _logger.LogWarning("Sending of command {CommandId} was cancelled.", command.Id);
}
Что означает
- sendCts.Token
- catch (OperationCanceledException) when (sendCts.IsCancellationRequested)
Что это означает этот код с загадочным sendCts.TokenОтвет
Разберем ваш вопрос по частям.
Что такое
sendCts.TokenВ коде объявлена строка:
csharpusing var sendCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);Здесь создается новый объект
sendCcts, который является связанным (linked) источником токена отмены.
Что он делает: Он создает специальный «токен» (объект типа
CancellationToken), который будет считаться отмененным в двух случаях:
- Если будет отменен внешний токен
stoppingToken.- Этот токен передается сервису хостом приложения при его остановке (например, при выключении компьютера или команды
dotnet stop).- Это сигнал для всего сервиса: «Пора завершать работу».
- Если сам объект
sendCtsбудет отменен вручную где-то в коде (в данном примере этого не происходит, но такая возможность заложена).Зачем это нужно? В асинхронном программировании есть операции, которые могут выполняться долго (например, запись в базу данных, вызов внешнего API,
отправка сообщения в очередь). Чтобы их можно было прервать, они принимают на вход
CancellationToken. Передавая_bus.SendAsync(command, sendCts.Token)этот токен, вы даете команду шине сообщений: "Попробуй отправить сообщение,но если этот токен будет отменен до завершения операции — прекрати её".
Почему не используют просто
stoppingToken? Использование отдельногоsendCtsизолирует операцию отправки от задержкиTask.Delay.Если бы вы передали
stoppingTokenв оба места, то отмена сервиса мгновенно прервала бы иSendAsync, иDelay.С текущим подходом, если
SendAsyncвыполняется дольше 5 секунд, аstoppingTokenсработал, тоTask.Delayне будет ждать свои 5 секунд,а сразу завершится, позволив циклу проверить условие выхода и начать завершение сервиса.
Таким образом,
sendCts.Token— это "контракт" на отмену, который мы передаем методуSendAsync, чтобы он мог быть прерван либо по нашему запросу,либо из-за общей остановки сервиса.
Что означает конструкция
catch (OperationCanceledException) when (sendCts.IsCancellationRequested)Это пример так называемого фильтра исключений (exception filter), появившегося в C# 6.0. Давайте разберем его по словам.
1.
catch (OperationCanceledException)Эта часть говорит: "Лови исключение типа
OperationCanceledException". Это стандартное исключение, которое выбрасывают асинхронные методы,когда обнаруживают, что их операция была отменена через
CancellationToken.2.
when (sendCts.IsCancellationRequested)Это фильтр. Он проверяет истинность выражения после слова
when. Код внутри блокаcatchвыполнится только в том случае,если одновременно произошли два события:
- Было выброшено исключение
OperationCanceledException.- Условие
sendCts.IsCancellationRequestedвернулоtrue.Свойство
IsCancellationRequestedуCancellationTokenSourceстановитсяtrueв тот момент, когда вызывается методsendCts.Cancel().Как это работает вместе?
Когда метод
_bus.SendAsync(...)получает вашsendCts.Tokenи видит, что он отменен (потому чтоstoppingTokenбыл отменен хостом),он прекращает свою работу и выбрасывает
OperationCanceledException.Исполнение переходит в блок
catch. Фильтрwhen (sendCts.IsCancellationRequested)проверяет, почему произошла отмена.Поскольку
sendCtsсвязан сstoppingToken, его свойствоIsCancellationRequestedтакже станетtrue.Условие фильтра выполняется, и код логирует предупреждение: "Отправка команды {Id} была отменена".
Почему это важно?
Представьте, что
SendAsyncможет выброситьOperationCanceledExceptionи по другой причине (например,внутренний таймаут или ошибка в реализации шины). Без фильтра
whenвы бы поймали это исключение и ошибочно подумали бы,что это результат штатной остановки сервиса. Фильтр позволяет обработать только ту отмену, которая связана с вашим
sendCts,игнорируя все остальные причины возникновения этого исключения.
После обработки исключения цикл
whileпродолжается, снова проверяет!stoppingToken.IsCancellationRequestedи,поскольку сигнал остановки уже получен, завершает свою работу.
Комментариев нет:
Отправить комментарий