在 .NET 或 .NET Core 应用中,若需在不依赖 Windows 服务、独立进程的前提下实现后台处理,Hangfire 是最成熟、简单的方案之一 —— 它可直接嵌入现有应用(如 ASP.NET Core Web 应用),无需额外部署,同时提供任务持久化、监控和重试能力。以下从 核心优势、快速集成步骤、关键用法 三方面,梳理如何用 Hangfire 实现后台处理:
一、为什么选择 Hangfire(核心优势)
- 零额外部署:可直接嵌入 Web 应用、控制台应用,无需单独部署 Windows 服务或计划任务;
- 任务持久化:支持 SQL Server、MySQL、Redis 等存储,应用重启后未执行的任务不会丢失;
- 可视化监控:自带 Dashboard 控制台,可实时查看任务状态(成功 / 失败 / 排队)、执行日志、重试记录;
- 多任务类型支持:覆盖常见后台场景(一次性任务、定时任务、周期性任务);
- 高可靠性:自动重试失败任务,支持任务优先级(队列区分),避免单点故障。
Install-Package Hangfire.AspNetCore
Install-Package Hangfire.MySqlStorage
#region Hangfire与MySQL
string connString = configuration.GetConnectionString("Hangfire")
?? throw new ArgumentException("未找到Hangfire连接字符串");
context.Services.AddHangfire(config =>
{
config.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings();
config.UseStorage(
new MySqlStorage(connString, new MySqlStorageOptions()));
});
context.Services.AddHangfireServer();
context.Services.AddTransient<IBackgroundJobClient, BackgroundJobClient>();
#endregion
#region 定义Queues
context.Services.AddHangfireServer(options =>
{
options.Queues = new[] { "maintenance-plan-overtime" };
options.WorkerCount = 1;
});
#endregion
}
public override async Task OnApplicationInitializationAsync(
ApplicationInitializationContext context)
{
await context.AddBackgroundWorkerAsync<LimitRecordWorker>();
}
public override Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context)
{
const string NAME = "HangfirePeriodicBackgroundWorkerAdapter<BackgroundJobWorker>.DoWorkAsync";
RecurringJob.RemoveIfExists(NAME);
return base.OnPostApplicationInitializationAsync(context);
}
如果你想使用 Hangfire Dashboard 来查看和管理后台任务,你需要在 Configure
方法中添加相应的中间件:
app.UseHangfireDashboard();
定时任务:
public class LimitRecordWorker : HangfireBackgroundWorkerBase
{
private readonly IAbpDistributedLock _distributedLock;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IBackgroundJobManager _backgroundJobManager;
public LimitRecordWorker(IAbpDistributedLock distributedLock,
IUnitOfWorkManager unitOfWorkManager,
IConfiguration configuration,
IBackgroundJobManager backgroundJobManager)
{
_distributedLock = distributedLock;
_unitOfWorkManager = unitOfWorkManager;
RecurringJobId = nameof(LimitRecordWorker);
_backgroundJobManager = backgroundJobManager;
CronExpression = configuration["Crons:LimitRecordCron"];
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = default)
{
await using (var handle = await _distributedLock
.TryAcquireAsync("LimitRecordDistributedLock", TimeSpan.FromSeconds(10)))
{
if (handle != null)
{
using (var uow = LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>().Begin())
{
Log.Information("LimitRecordWorker" + DateTime.Now);
for (int i = 0; i <= 5; i++)
{
MaintenancePlanOverTimeArgs workProcedureLeaderArgs = new MaintenancePlanOverTimeArgs();
workProcedureLeaderArgs.Serial = i;
await _backgroundJobManager.EnqueueAsync(workProcedureLeaderArgs, delay: TimeSpan.FromHours(workProcedureLeaderArgs.OverTime % 24));
}
await uow.CompleteAsync();
}
}
}
}
}
队列服务:
[Queue("maintenance-plan-overtime")]
public class MaintenancePlanOverTimeJob : AsyncBackgroundJob<MaintenancePlanOverTimeArgs>, ITransientDependency
{
public MaintenancePlanOverTimeJob()
{
}
public override async Task ExecuteAsync(MaintenancePlanOverTimeArgs args)
{
Log.Information("MaintenancePlanOverTimeJob:" + args.Serial + "--" + DateTime.Now);
}
}
public class MaintenancePlanOverTimeArgs
{
public int Serial { get; set; }
public List<Guid> EmployeeIds { get; set; }
public Guid DepartmentId { get; set; }
public Guid? WorkProcedureId { get; set; }
public Guid? DeviceDetailTypeId { get; set; }
public double OverTime { get; set; }
}
效果: