суббота, 13 июня 2026 г.

ExecuteAsync, MyBackgroundServicesApp.slnx

ExecuteAsync, MyBackgroundServicesApp.slnx

https://giga.chat/link/gcsqkVfqAN

D:\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

В коде объявлена строка:

csharp
using var sendCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);

Здесь создается новый объект sendCcts, который является связанным (linked) источником токена отмены.

  1. Что он делает: Он создает специальный «токен» (объект типа CancellationToken), который будет считаться отмененным в двух случаях:

    • Если будет отменен внешний токен stoppingToken.
    • Этот токен передается сервису хостом приложения при его остановке (например, при выключении компьютера или команды dotnet stop).
    • Это сигнал для всего сервиса: «Пора завершать работу».
    • Если сам объект sendCts будет отменен вручную где-то в коде (в данном примере этого не происходит, но такая возможность заложена).
  2. Зачем это нужно? В асинхронном программировании есть операции, которые могут выполняться долго (например, запись в базу данных, вызов внешнего API,

  3. отправка сообщения в очередь). Чтобы их можно было прервать, они принимают на вход CancellationToken. Передавая _bus.SendAsync(command, sendCts.Token) этот токен, вы даете команду шине сообщений: "Попробуй отправить сообщение,

  4. но если этот токен будет отменен до завершения операции — прекрати её".

  5. Почему не используют просто stoppingToken? Использование отдельного sendCts изолирует операцию отправки от задержки Task.Delay.

  6. Если бы вы передали stoppingToken в оба места, то отмена сервиса мгновенно прервала бы и SendAsync, и Delay.

  7. С текущим подходом, если SendAsync выполняется дольше 5 секунд, а stoppingToken сработал, то Task.Delay не будет ждать свои 5 секунд,

  8. а сразу завершится, позволив циклу проверить условие выхода и начать завершение сервиса.

Таким образом, 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 и,

поскольку сигнал остановки уже получен, завершает свою работу.

Комментариев нет:

Отправить комментарий