Я искал использование 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). Кажется странным, что у меня просто пустой рабочий класс.
Любые советы приветствуются.





Вам не нужен этот пустой рабочий класс. Простой вызов 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! Я удалил рабочий класс, работает нормально. так что мне просто нужен сервисный проект оболочки, лол :-)