HangFire как служба Windows для .NET 6

Я искал использование HangFire в качестве службы Windows для .NET 6, официальному документу около 10 лет. В других примерах не указано, как настроить рабочую службу. Во всяком случае, это моя среда - у меня есть веб-приложение с приложением API. Приложение API — это место, где фоновые задания будут поставлены в очередь в HangFire, но я бы хотел, чтобы фактическая обработка выполнялась на другом сервере, например на сервере приложений. Итак, моя цель — создать службу Windows, чтобы просто запускать сервер HangFire и продолжать позволять приложению API управлять созданием заданий.

Я создал новый проект Worker Service, и вот мой код:

public class Program
{
    public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
       Host.CreateDefaultBuilder(args)
           .ConfigureLogging(logging =>
           {
               logging.ClearProviders();
               logging.AddConsole();
               logging.AddEventLog();
           })
           // Essential to run this as a window service
           .UseWindowsService()
           .ConfigureServices(configureServices);

    private static void configureServices(HostBuilderContext context, IServiceCollection services)
    {
        var defaultConnection = context.Configuration.GetConnectionString("DefaultConnection");
        var hangFireConnection = context.Configuration.GetConnectionString("HangFireConnection");
        AppSettings appSettings = context.Configuration.GetSection("AppSettings").Get<AppSettings>();

        services.AddLogging();
        services.AddHangfire(configuration => configuration
            .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
            .UseSimpleAssemblyNameTypeSerializer()
            .UseRecommendedSerializerSettings()
            .UseSqlServerStorage(hangFireConnection, new SqlServerStorageOptions
            {
                CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
                SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
                QueuePollInterval = TimeSpan.Zero,
                UseRecommendedIsolationLevel = true,
                DisableGlobalLocks = true
            }));
        services.AddHangfireServer();

        services.AddDbContext<PpContext>(options => options.UseSqlServer(defaultConnection), ServiceLifetime.Transient);
        services.AddScoped<ExceptionNotifier>();
        services.AddHostedService<HangFireWorker>();


        JobStorage.Current = new SqlServerStorage(hangFireConnection);
        RecurringJob.AddOrUpdate<ExceptionNotifier>("exception-notification", x => x.NotifyByHour(), "0 * * * *"); //runs every hour on the hour
    }
}

Как видите, у меня есть одно повторяющееся задание, которое выполняется каждый час, в течение часа.

Тогда для класса HangFireWorker это то, что у меня есть:

public class HangFireWorker : BackgroundService
{
    private readonly ILogger<HangFireWorker> _logger;

    public HangFireWorker(ILogger<HangFireWorker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        //while (!stoppingToken.IsCancellationRequested)
        //{
        //    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
        //    await Task.Delay(1000, stoppingToken);
        //}


        //there is nothing to do here, hangfire already takes cares of all?
        await Task.Delay(0);
    }
}

Итак, мой вопрос: нужно ли мне что-то делать в основном рабочем классе? В функции ExecuteAsync()? Я имею в виду, что проект работает просто отлично, как сейчас. Я вижу, что сервер успешно зарегистрирован на панели инструментов (приложение API). Кажется странным, что у меня просто пустой рабочий класс.

Любые советы приветствуются.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вам не нужен этот пустой рабочий класс. Простой вызов AddHangfireServer создаст рабочего.

Вы действительно можете увидеть, что сервер зарегистрирован, если откроете исходный код AddHangfireServer:

public static IServiceCollection AddHangfireServer(
  [NotNull] this IServiceCollection services,
  [NotNull] Action<IServiceProvider, BackgroundJobServerOptions> optionsAction)
{
  if (services == null)
    throw new ArgumentNullException(nameof (services));
  return optionsAction != null ? HangfireServiceCollectionExtensions.AddHangfireServerInner(services, (JobStorage) null, (IEnumerable<IBackgroundProcess>) null, optionsAction) : throw new ArgumentNullException(nameof (optionsAction));
}

...

private static IServiceCollection AddHangfireServerInner(
  [NotNull] IServiceCollection services,
  [CanBeNull] JobStorage storage,
  [CanBeNull] IEnumerable<IBackgroundProcess> additionalProcesses,
  [NotNull] Action<IServiceProvider, BackgroundJobServerOptions> optionsAction)
{
  services.AddTransient<IHostedService, BackgroundJobServerHostedService>((Func<IServiceProvider, BackgroundJobServerHostedService>) (provider =>
  {
    BackgroundJobServerOptions options = new BackgroundJobServerOptions();
    optionsAction(provider, options);
    return HangfireServiceCollectionExtensions.CreateBackgroundJobServerHostedService(provider, storage, additionalProcesses, options);
  }));
  return services;
}

...

private static BackgroundJobServerHostedService CreateBackgroundJobServerHostedService(
  IServiceProvider provider,
  JobStorage storage,
  IEnumerable<IBackgroundProcess> additionalProcesses,
  BackgroundJobServerOptions options)
{
  HangfireServiceCollectionExtensions.ThrowIfNotConfigured(provider);
  storage = storage ?? provider.GetService<JobStorage>() ?? JobStorage.Current;
  additionalProcesses = additionalProcesses ?? provider.GetServices<IBackgroundProcess>();
  options.Activator = options.Activator ?? provider.GetService<JobActivator>();
  options.FilterProvider = options.FilterProvider ?? provider.GetService<IJobFilterProvider>();
  options.TimeZoneResolver = options.TimeZoneResolver ?? provider.GetService<ITimeZoneResolver>();
  IBackgroundJobFactory factory;
  IBackgroundJobStateChanger stateChanger;
  IBackgroundJobPerformer performer;
  HangfireServiceCollectionExtensions.GetInternalServices(provider, out factory, out stateChanger, out performer);
  IHostApplicationLifetime service = provider.GetService<IHostApplicationLifetime>();
  return new BackgroundJobServerHostedService(storage, options, additionalProcesses, factory, performer, stateChanger, service);
}

...

public class BackgroundJobServerHostedService : IHostedService, IDisposable
{

AddHangfireServer просто зарегистрирует BackgroundJobServerHostedService и все сделает сам.

Хорошая стратегия для выяснения подобных вещей — когда-нибудь углубиться в фактический исходный код.

Это также задокументировано на github пакета Hangfire.AspNetCore:

Обработка фоновых задач внутри веб-приложения…

Вы можете выполнять фоновые задачи в любой OWIN-совместимой среде приложений, включая ASP.NET MVC, ASP.NET Web API, FubuMvc, Nancy и т. д. Забудьте о выгрузках AppDomain, проблемах Web Garden и Web Farm — Hangfire надежен для веб-приложений от нуля, даже на виртуальном хостинге.

app.UseHangfireServer(); … или где-нибудь еще

В консольных приложениях, службе Windows, рабочей роли Azure и т. д.

using (new BackgroundJobServer())
{
    Console.WriteLine("Hangfire Server started. Press ENTER to exit...");
    Console.ReadLine();
}

https://github.com/HangfireIO/Hangfire

Спасибо sommmen! Я удалил рабочий класс, работает нормально. так что мне просто нужен сервисный проект оболочки, лол :-)

Franky 23.01.2023 16:14

Другие вопросы по теме