Hangfire 启动需三步:注册服务(AddHangfire+存储)、启用后台进程(AddHangfireServer)、启用中间件(UseHangfireDashboard);延时用Schedule而非ContinueWith;周期任务需校准Cron与时区,并防并发。

Hangfire 启动失败:Startup.cs 或 Program.cs 里漏了这三步
Hangfire 不是 NuGet 装完就能跑的,缺任何一步都会报 InvalidOperationException: No storage registered 或直接 500。核心就三点:注册服务、配置存储、启用中间件。
- 必须调用
AddHangfire(传IServiceCollection)并指定存储,比如UseSqlServerStorage或UseMemoryStorage(仅开发) - 必须调用
AddHangfireServer,否则后台作业进程根本不会启动,任务永远卡在Enqueued - 必须在
app.UseEndpoints前加app.UseHangfireDashboard(),否则/hangfire路由 404
用 SQLite 或内存存储做本地测试时,别写错连接字符串——UseMemoryStorage() 不接受参数,写成 UseMemoryStorage("xxx") 会编译失败;SQL Server 则必须确保连接字符串能真实连上,不然启动时就抛异常。
延时任务怎么发:用 BackgroundJob.Schedule,别碰 ContinueWith
想让任务 10 分钟后执行?不是等上一个任务完成再延时,而是从“现在”起算绝对延迟。最稳的方式是 BackgroundJob.Schedule,它生成的是独立作业,不依赖上游状态。
-
BackgroundJob.Schedule—— 推荐,语义清晰,支持泛型服务解析(x => x.DoWork(), TimeSpan.FromMinutes(10)) - 别用
ContinueWith模拟延时:它本质是链式依赖,前序失败整个链断掉,且延时逻辑混在回调里,难调试、难重试 - 注意时间精度:Hangfire 默认轮询间隔是 15 秒,所以“精确到秒级延时”不现实;如果需要 sub-second 级调度,得换 Quartz
示例中 IMyService 必须已注册为 Scoped 或 Transient,Hangfire 内部用 IServiceScopeFactory 创建实例,注册成 Singleton 可能引发状态污染。
周期任务不能只靠 RecurringJob.AddOrUpdate,得配好 Cron 表达式和 JobFilter
RecurringJob.AddOrUpdate 是挂周期任务的入口,但光写这句,任务可能永不触发——常见原因是 Cron 错、时区没对、或没处理并发。
- Cron 表达式默认按服务器本地时区解析,不是 UTC;如果部署在 Docker 容器里,容器时区可能为 UTC,而你按北京时间写的
"0 0 * * *"(每天 0 点),实际按 UTC 执行就是北京时间早上 8 点 - 加
DisableConcurrentExecution过滤器,否则同一任务多个实例可能并发跑,比如数据库清理任务重复删数据 - 首次添加后,任务不会立刻执行,要等到下一个匹配时间点;想马上验证,可临时改成
"* * * * *"(每分钟一次),确认日志有输出再改回
表达式里别写 "0/5 * * * *" 这种“每 5 分钟”,Hangfire 的 Cron 解析器不支持间隔语法(部分版本会静默忽略),老实用 "*/5 * * * *" 或生成完整列表。
后台作业执行失败:看 Dashboard 的 Retries 和日志,别只盯着代码
任务显示 Failed 并重试三次?先别急着改业务逻辑。Hangfire 的失败链路比想象中长:序列化 → 队列 → 反序列化 → 依赖注入 → 方法执行 → 数据库提交。
- 检查 Dashboard 里失败作业的
Exception字段:常见是NullReferenceException(DI 未注册服务)、SqlException(连接超时或事务冲突)、JsonSerializationException(参数含不可序列化对象如HttpContext) - 参数必须可序列化:不能传
IFormFile、Stream、Action委托,得提前转成 byte[] 或路径字符串 - 数据库事务要注意:Hangfire 默认不在事务内执行作业,如果你在 Web 请求里用
DbContext提交了数据,又在后台任务里查,可能读到未提交状态;需手动控制隔离级别或加重试逻辑
最易被忽略的是 Hangfire Server 的生命周期——它绑定在 WebHost 上,如果应用池回收、K8s Pod 重启、或 dotnet watch 热重载,Server 就停了,正在执行的任务会被标记为 Deleted 或 Failed,但 Dashboard 不提示“Server 已离线”,得自己盯监控。











