APScheduler定时任务重复执行求助:周五8:05触发却执行两次
我配置了一个APScheduler定时任务,期望每周五上午8:05执行,并设置了15分钟的jitter(抖动)。代码如下:
from apscheduler.schedulers.background import BackgroundScheduler import requests import time # 补充缺失的导入 sched = BackgroundScheduler() def WOL(): requests.get("https://example.com/WOL") sched.add_job(WOL, 'cron', hour=8, minute=5, jitter=900, day_of_week='fri') sched.start() try: while True: time.sleep(2) except (KeyboardInterrupt, SystemExit): sched.shutdown()
但任务实际执行了两次,当日运行日志如下:
2024-01-05 08:04:17,657 - INFO - Running job "WOL (trigger: cron[day_of_week='fri', hour='8', minute='5'], next run at: 2024-01-05 08:13:47 PST)" (scheduled at 2024-01-05 08:04:17.646354-08:00)
2024-01-05 08:04:19,848 - INFO - Job "WOL (trigger: cron[day_of_week='fri', hour='8', minute='5'], next run at: 2024-01-05 08:13:47 PST)" executed successfully
2024-01-05 08:13:47,596 - INFO - Running job "WOL (trigger: cron[day_of_week='fri', hour='8', minute='5'], next run at: 2024-01-12 08:12:15 PST)" (scheduled at 2024-01-05 08:13:47.591469-08:00)
2024-01-05 08:13:49,608 - INFO - Job "WOL (trigger: cron[day_of_week='fri', hour='8', minute='5'], next run at: 2024-01-12 08:12:15 PST)" executed successfully
任务分别在8:04和8:13执行了,请问调度配置哪里出了问题?
原因与解决方案
问题原因
当在cron触发器中搭配jitter参数时,如果调度器启动时间早于cron指定的目标时间(比如周五8:00启动,目标时间为8:05),调度器会错误生成两次运行计划:
- 第一次以启动时间为基准添加随机抖动,导致任务在目标时间前提前执行;
- 第二次以cron目标时间为基准添加随机抖动,属于符合预期的周五8:05±15分钟窗口内执行。
这是因为APScheduler默认会基于当前启动时间计算首次运行的抖动时间,忽略了cron表达式的固定时间约束。
解决方案
通过指定start_date参数,强制首次运行时间不早于cron目标时间(周五8:05),让调度器仅生成符合预期的单次周度计划:
from apscheduler.schedulers.background import BackgroundScheduler import requests import time from datetime import datetime, timedelta sched = BackgroundScheduler() def WOL(): requests.get("https://example.com/WOL") # 计算首个符合要求的周五8:05时间点 now = datetime.now() # weekday()返回0=周一,4=周五,计算距离下一个周五的天数 days_to_friday = (4 - now.weekday() + 7) % 7 next_friday = now + timedelta(days=days_to_friday) next_friday = next_friday.replace(hour=8, minute=5, second=0, microsecond=0) # 如果当前时间已过本周周五8:05,自动顺延至下周 if now >= next_friday: next_friday += timedelta(weeks=1) # 添加任务时指定start_date约束首次运行时间 sched.add_job(WOL, 'cron', hour=8, minute=5, jitter=900, day_of_week='fri', start_date=next_friday) sched.start() try: while True: time.sleep(2) except (KeyboardInterrupt, SystemExit): sched.shutdown()
额外注意事项
- 补充代码中缺失的
import requests和import time,避免运行报错; - 确保调度器进程仅启动一次,防止重复添加任务导致多次执行;
- 若无需处理错过的任务,可设置
misfire_grace_time=0,但不推荐用于需保证执行的定时任务。
内容的提问来源于stack exchange,提问作者Bijan

