如何在APScheduler定时任务中正确使用aiohttp的ClientSession?
现有实现的两个核心问题
- 装饰器参数提前求值:
@sched.scheduled_job的args参数会在tasks模块加载时立即计算,此时main.py中的session还未绑定到sched,参数会被赋值为None,任务执行时直接报错。 - 非序列化参数无法存入RedisJobStore:ClientSession是内存异步对象,无法被序列化存储到Redis,你将其作为任务参数传递时,APScheduler会尝试序列化参数写入Redis,直接抛出序列化异常。
方案1:直接复用调度器绑定的session(最小改动适配现有代码)
不要将session作为任务参数传递,直接在任务运行时从调度器实例读取已绑定的session属性,规避序列化和提前求值问题:
修改后的tasks.py代码
# tasks.py from scheduler import scheduler as sched @sched.scheduled_job('cron', minute=30) async def task_with_request(): # 任务运行时再读取session,此时sched.session已在main中完成绑定 async with sched.session.get('https://someapi.com') as response: page = await response.text() # 后续业务逻辑
修改后的main.py代码
# main.py import asyncio import aiohttp from scheduler import scheduler as sched if __name__ == '__main__': async def run(): async with aiohttp.ClientSession() as session: # 绑定session到调度器实例 sched.session = session sched.start() # 阻塞等待调度器运行(适配asyncio上下文) while True: await asyncio.sleep(3600) asyncio.run(run())
注意:你原来的__name__ == '__name__'是笔误,正确应为__name__ == '__main__',且AsyncIOScheduler的start()需要运行在asyncio事件循环内,调整后的写法更符合异步规范
方案2:全局独立维护ClientSession(解耦更规范)
将ClientSession和调度器分开管理,避免给调度器绑定自定义属性,逻辑更清晰:
新增session.py
# session.py import aiohttp from typing import Optional # 全局session实例 _session: Optional[aiohttp.ClientSession] = None async def init_client_session(): global _session _session = aiohttp.ClientSession() async def get_client_session() -> aiohttp.ClientSession: return _session async def close_client_session(): global _session if _session and not _session.closed: await _session.close()
调整后的tasks.py
# tasks.py from scheduler import scheduler as sched from session import get_client_session @sched.scheduled_job('cron', minute=30) async def task_with_request(): session = await get_client_session() async with session.get('https://someapi.com') as response: page = await response.text() # 后续业务逻辑
调整后的main.py
# main.py import asyncio from scheduler import scheduler as sched from session import init_client_session, close_client_session if __name__ == '__main__': async def run(): await init_client_session() try: sched.start() while True: await asyncio.sleep(3600) finally: await close_client_session() asyncio.run(run())
关于ClientSession功能的说明
上述两种实现都完全符合aiohttp官方的复用规则:
- 整个应用生命周期内仅创建一个ClientSession实例
- 所有请求都在同一个事件循环内复用该实例
- 应用退出时自动关闭Session释放资源
不会破坏ClientSession的任何原有功能,也不会出现资源泄漏、连接池异常等问题。
内容的提问来源于stack exchange,提问作者Mathias Sven
相关产品推荐
相关产品推荐

