如何按需调度带定时器的方法调用 实现条件触发后48小时执行一次
条件触发的48小时单次调度实现方案
需求拆解
- 触发时机:自定义业务条件满足时发起调度请求
- 执行规则:对应业务方法调度完成后48小时内仅可执行1次,重复触发调度也不会重复执行
- 生命周期:调度场景可无限次创建、执行,无次数上限
Quartz.NET落地实现
前置准备
先创建调度执行记录表,存储在共享存储(数据库/Redis均可),核心字段如下:
ScheduleKey:调度唯一标识,可按业务场景编码_业务主键规则生成,用于唯一区分不同的调度场景ExecuteStatus:调度状态(待执行/已执行/执行失败)LastExecuteTime:最近一次实际执行时间
调度创建逻辑
当业务条件满足时,执行以下流程:
- 按规则生成当前场景的
ScheduleKey - 查询调度记录:若存在对应
ScheduleKey的记录,且LastExecuteTime距当前时间小于48小时,直接返回,不创建新调度 - 若不存在符合条件的记录,新增待执行状态的调度记录
- 调用Quartz.NET创建一次性调度任务:
// 绑定调度标识到Job参数 IJobDetail businessJob = JobBuilder.Create<YourTargetJob>() .WithIdentity($"job_{scheduleKey}", "custom_schedule_group") .UsingJobData("schedule_key", scheduleKey) .Build(); // 触发器设为立即执行,可根据需求调整为延迟触发 ITrigger jobTrigger = TriggerBuilder.Create() .WithIdentity($"trigger_{scheduleKey}", "custom_schedule_group") .StartNow() .Build(); await scheduler.ScheduleJob(businessJob, jobTrigger);
Job执行逻辑(核心幂等控制)
Job执行时先做状态校验,避免重复执行:
public class YourTargetJob : IJob { public async Task Execute(IJobExecutionContext context) { string scheduleKey = context.JobDetail.JobDataMap.GetString("schedule_key"); // 分布式场景下此处需加行锁/分布式锁,避免并发问题 var scheduleRecord = await _dbContext.ScheduleRecords .FirstOrDefaultAsync(r => r.ScheduleKey == scheduleKey); // 校验不通过直接返回 if (scheduleRecord == null || scheduleRecord.ExecuteStatus != ExecuteStatus.Pending || (scheduleRecord.LastExecuteTime.HasValue && DateTime.Now.Subtract(scheduleRecord.LastExecuteTime.Value).TotalHours < 48)) { return; } // 先更新状态再执行业务逻辑,避免异常导致重复执行 scheduleRecord.ExecuteStatus = ExecuteStatus.Completed; scheduleRecord.LastExecuteTime = DateTime.Now; await _dbContext.SaveChangesAsync(); // 执行目标业务方法 await YourBusinessMethod(); } }
异常处理
- 若业务方法执行失败需要重试,可在Job内直接加重试逻辑,不要新建调度,避免突破48小时单次的限制
- 若不需要重试,可将调度记录标记为失败,后续业务条件再次满足时可自动创建新调度,只要距上次执行超过48小时即可
轻量场景替代方案
如果是单体应用,不需要分布式调度能力,可直接用ASP.NET Core自带的IHostedService+内存队列实现,核心逻辑和上述方案一致,仅需将Quartz.NET的调度替换为后台任务轮询队列执行即可,无需引入额外组件。
注意事项
- 分布式部署场景必须使用共享存储存储调度记录,查询和更新状态时加锁,避免多实例重复执行
- 历史调度记录可定期归档,不影响新调度的正常创建
内容的提问来源于stack exchange,提问作者tokechu
相关产品推荐
相关产品推荐

