вторник, 26 мая 2026 г.

Qwen, WorkerServices, DedicatedWorker

Qwen, WorkerServices, DedicatedWorker

https://chat.qwen.ai/s/350fc6ec-dc1b-4fa5-9d49-351c5f52c222?fev=0.2.57

D:\Projects\VS\2605\BackgroundVersusWorker\Qwen\DedicatedWorker\DedicatedWorker.slnx
D:\Projects\VS\2605\BackgroundVersusWorker\Qwen\DedicatedWorker\DedicatedWorker01\DedicatedWorker01.csproj

------------------------------------------------------------------------------------
https://chat.qwen.ai/s/350fc6ec-dc1b-4fa5-9d49-351c5f52c222?fev=0.2.57
------------------------------------------------------------------------------------
DedicatedWorker/
├── Program.cs
├── Worker.cs              # Переименованный/доработанный Worker : BackgroundService
├── WorkerSettings.cs      # Настройки из appsettings.json
├── appsettings.json
└── DedicatedWorker.csproj
----------------------------------------
WorkerSettings.cs
----------------------------------------
public class WorkerSettings
{
    public int BatchSize { get; set; } = 50;
    public int ProcessingIntervalSeconds { get; set; } = 10;
}
----------------------------------------
appsettings.json
----------------------------------------
{
  "Logging": { "LogLevel": { "Default": "Information" } },
  "WorkerSettings": {
    "BatchSize": 100,
    "ProcessingIntervalSeconds": 5
  }
}
-----------------------------------------
Worker.cs
-----------------------------------------
public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IOptions<WorkerSettings> _settings;

    public Worker(ILogger<Worker> logger, IOptions<WorkerSettings> settings)
    {
        _logger = logger;
        _settings = settings;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("🚀 DedicatedWorker запущен. BatchSize={BatchSize}", _settings.Value.BatchSize);

        // Цикл обработки с явной обработкой ошибок и перезапуском
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await ProcessDataBatchAsync(_settings.Value.BatchSize, stoppingToken);
                
                // Пауза между итерациями
                await Task.Delay(TimeSpan.FromSeconds(_settings.Value.ProcessingIntervalSeconds), stoppingToken);
            }
            catch (OperationCanceledException)
            {
                _logger.LogInformation("🛑 Graceful shutdown requested.");
                break;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "💥 Ошибка обработки батча. Повтор через {Interval}с", _settings.Value.ProcessingIntervalSeconds);
                // В продакшене сюда часто добавляют экспоненциальную задержку или alert
                await Task.Delay(TimeSpan.FromSeconds(_settings.Value.ProcessingIntervalSeconds), stoppingToken);
            }
        }
    }

    private async Task ProcessDataBatchAsync(int batchSize, CancellationToken token)
    {
        _logger.LogInformation("📦 Обработка батча размером {Size}...", batchSize);
        // Симуляция тяжелой работы (запрос к БД, чтение очереди, вычисления)
        await Task.Delay(300, token);
        _logger.LogInformation("✅ Батч {Size} успешно обработан", batchSize);
    }
}
---------------------------------------
Program.cs
---------------------------------------
using DedicatedWorker;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

// Привязываем настройки из appsettings.json
builder.Services.Configure<WorkerSettings>(builder.Configuration.GetSection("WorkerSettings"));

// Регистрируем единственный хостовый сервис
builder.Services.AddHostedService<Worker>();

var host = builder.Build();
await host.RunAsync(); // Блокирует поток, пока не придёт сигнал остановки
--------------------------------------------------------------------------------------------------
Особенности
Нет HTTP-стека → меньше потребление памяти, быстрее старт.
Готов к запуску как системный сервис:
Linux: systemctl enable --now myworker
Windows: sc create MyWorker binPath="...\DedicatedWorker.exe"
Docker: restart: always + healthcheck
Идеально для горизонтального масштабирования (K8s Deployment, StatefulSet, CronJob).
---------------------------------------------------------------------------------------------------
Сводная таблица выбора
--------------------------------------
Критерий
BackgroundService в Web API
Отдельный Worker Service
Процесс
Один процесс с HTTP-сервером
Отдельный процесс/демон
DI/Конфигурация
Общая с основным приложением
Изолированная, настраивается отдельно
Масштабирование
Масштабируется вместе с API
Независимое (можно 10 реплик воркера, 2 реплики API)
Нагрузочное влияние
Фоновая задача может конкурировать за CPU/RAM с HTTP-запросами
Полная изоляция ресурсов
Типичные задачи
Очистка сессий, отложенные уведомления, периодическая синхронизация
Обработка RabbitMQ/Kafka, ETL, генерация отчётов, мониторинг
Деплой
Вместе с API
systemd, Windows Service, K8s Worker, Docker
---------------------------------------------------------------------
Практические рекомендации
---------------------------------------------------------------------
Никогда не используйте while(true) { Thread.Sleep(); }.
Используйте PeriodicTimer или Task.Delay(..., token), чтобы корректно реагировать на отмену.
Обработка исключений:
BackgroundService по умолчанию останавливает весь хост при незакрытом Exception в ExecuteAsync. 
Обёртывайте бизнес-логику в try/catch, если хотите, чтобы процесс продолжил работу.
Если нужен HTTP + фон → создавайте webapi проект и регистрируйте AddHostedService<T>(). 
Не смешивайте dotnet new worker с MapControllers().
В .NET 9 добавлен BackgroundService.ExecuteAsync с поддержкой IAsyncDisposable и улучшенным graceful shutdown. 
Паттерны выше полностью совместимы.

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

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