关键词:APScheduler、BackgroundScheduler、定时任务、非阻塞、线程池、触发器、任务持久化
适用版本:APScheduler 3.x / 4.x(Python 3.8+)
- 开场 30 秒
写 Web 项目时,你是不是也遇到过这样的需求:
- 每 30 分钟刷新一次缓存
- 每天 2:00 清理临时文件
- 用户下单后 15 分钟未支付则自动关闭订单
如果再用 while True + sleep 手写线程,不仅费电,还容易翻车。
APScheduler 官方给出的「后台调度器」——BackgroundScheduler,30 行代码就能让定时任务在独立线程里乖乖跑,主线程完全不被阻塞。今天这篇博客带你从安装、配置、触发器、持久化、线程模型一路讲到源码,包教包会。
- 安装 & Hello World
pip install apscheduler
最小可运行示例(每 3 秒打印一次时间):
from apscheduler.schedulers.background import BackgroundScheduler
import time, datetime
def tick():
print('Tick! The time is: %s' % datetime.datetime.now())
scheduler = BackgroundScheduler()
scheduler.add_job(tick, 'interval', seconds=3)
scheduler.start()
try:
while True: # 主线程完全空闲,可以干别的
time.sleep(2)
print('Main thread is free...')
except (KeyboardInterrupt, SystemExit):
scheduler.shutdown()
输出:
Main thread is free...
Tick! The time is: 2026-01-24 14:21:12
Main thread is free...
Tick! The time is: 2026-01-24 14:21:15
...
结论:调度任务发生在后台线程,主线程继续处理业务逻辑,实现真正的「非阻塞」。
- 架构鸟瞰图(3 大核心 + 1 个仓库)
┌--------------┐ add_job() ┌-------------┐
│ Scheduler │--------------------►│ JobStore │ ← 保存任务元数据
│ (调度器) │ └-------------┘
└------┬-------┘
│submit
▼
┌--------------┐ run_job() ┌-------------┐
│ Executor │--------------------►│ 业务函数 │
│ (线程/进程池) │ └-------------┘
└--------------┘
▲
│trigger
┌--------------┐
│ Trigger │ 决定「下一次」何时触发
└--------------┘
- Scheduler:大脑,负责任务生命周期、事件循环。
- Trigger:闹钟,有三种口味:
date只闹一次interval固定间隔cron类 Linux crontab 表达式
- Executor:干活的,把任务丢进线程池(默认)或进程池。
- JobStore:仓库,默认放内存,也可换成 MySQL、Redis、MongoDB 实现宕机续跑。
- 触发器深度拆解
3.1 date——一次性任务
from apscheduler.triggers.date import DateTrigger
trigger = DateTrigger(run_date='2026-02-14 15:30:00')
scheduler.add_job(tick, trigger)
3.2 interval——循环间隔
scheduler.add_job(tick, 'interval', hours=2, minutes=30, seconds=15,
start_date='2026-01-24 14:00:00',
end_date='2026-12-31 23:59:59')
3.3 cron——最强日历
# 每周六 05:30 & 20:30 运行
scheduler.add_job(tick, 'cron',
day_of_week='sat',
hour='5,20',
minute=30,
timezone='Asia/Shanghai')
支持标准 crontab 写法:* */3 1-5 2-6。
- Executor:线程 or 进程?
默认ThreadPoolExecutor(max_workers=10),IO 密集型足够。
如果任务是 CPU 密集型,换进程池:
from apscheduler.executors.pool import ProcessPoolExecutor
executors = {
'default': ProcessPoolExecutor(4) # 4 核并行
}
scheduler = BackgroundScheduler(executors=executors)
注意:进程池要求函数可序列化,即在
if __name__ == '__main__':里定义。
- JobStore:让任务「重启不丢」
内存仓库存放在dict,进程重启灰飞烟灭。
生产环境请把任务落库,以 MySQL 为例:
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
jobstores = {
'default': SQLAlchemyJobStore(url='mysql+pymysql://user:pwd@127.0.0.1/apscheduler')
}
scheduler = BackgroundScheduler(jobstores=jobstores)
- 调度器启动时会自动把未完成任务加载回来;
- 配合
misfire_grace_time可补偿因宕机错过的执行。
- 常用配置参数速查表
| 参数 | 说明 | 示例 | |----|------|------| |coalesce| 宕机期间积压多次,合并为一次执行 |coalesce=True| |max_instances| 同一任务并发实例数 |max_instances=3| |misfire_grace_time| 容错窗口(秒) |misfire_grace_time=600| |replace_existing| 重复add_job是否覆盖 |replace_existing=True|
- 源码走马观花(3.10 版本)
7.1 启动流程
scheduler.start()→
_real_add_job()写 JobStore →
_main_loop()在一个守护线程里循环: - 计算下一次触发时间(
trigger.get_next_fire_time) - 等待或立即提交到 Executor
- 任务执行完成回调
_run_job_success()更新下次触发
7.2 线程模型
# BackgroundScheduler 继承 BaseScheduler
class BackgroundScheduler(BaseScheduler):
def start(self, *a, **kw):
self._main_thread = threading.Thread(target=self._main_loop, daemon=True)
self._main_thread.start()
守护线程意味着:主线程退出时,调度线程不会阻止进程结束,符合“后台”语义。
- 实战:Flask 后台更新缓存
from flask import Flask
from apscheduler.schedulers.background import BackgroundScheduler
app = Flask(__name__)
scheduler = BackgroundScheduler()
CACHE = {}
def refresh_cache():
CACHE['ts'] = datetime.datetime.now().isoformat()
print('[JOB] cache refreshed at', CACHE['ts'])
scheduler.add_job(refresh_cache, 'interval', seconds=30)
scheduler.start()
@app.route('/')
def index():
return f'Cache ts: {CACHE.get("ts", "N/A")}'
if __name__ == '__main__':
app.run()
Flask 主线程专注处理 HTTP 请求,缓存刷新在后台悄悄完成,完美解耦。
- 常见踩坑 FAQ
| 现象 | 原因 | 解决 | |----|------|------| | 任务不执行 | 主线程瞬间结束 | 加while True或join()| | 重复执行 | 多进程启动时每个进程都start()| 保证只在 master 进程start| | 函数内日志不打印 | Executor 线程里未配置 logger | 给 APScheduler 单独配logging| | 修改任务时间不生效 | 默认replace_existing=False|add_job(..., replace_existing=True)|
- 小结 & 思维导图
- BackgroundScheduler = 非阻塞 + 线程池 + 可持久化
- 三步走:选 Trigger → 选 Executor → 选 JobStore
- 生产环境务必配置时区、misfire_grace_time 与数据库仓库
- 源码精髓:守护线程
_main_loop驱动事件表,Executor 负责任务执行
把这篇收藏起来,下次再写「定时脚本」时,直接
pip install apscheduler,30 秒上线,准点下班!
参考文献
51CTO《使用 Python BackgroundScheduler 的完整流程》
CSDN《Python任务调度库之apscheduler使用详解》
CSDN《BackgroundScheduler 使用原创》
CSDN《Python任务调度框架APScheduler详解》
CSDN《python调度框架APScheduler使用详解》