You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在APScheduler定时任务中正确使用aiohttp的ClientSession?

现有实现的两个核心问题

  1. 装饰器参数提前求值:@sched.scheduled_job的args参数会在tasks模块加载时立即计算,此时main.py中的session还未绑定到sched,参数会被赋值为None,任务执行时直接报错。
  2. 非序列化参数无法存入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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 09:45:04