是否可直接将Future状态存储在Application对象上?
嘿,这个问题我刚好有实际项目经验可以分享——答案是完全可以这么做,但在实际落地时得留意几个关键细节,不然容易踩坑:
1. 避免重复创建已完成的任务
你的示例代码里用getattr(app, 'task_obj', None)来判断任务是否存在,这个思路没问题,但有个小漏洞:当任务正常跑完或者被取消后,task_obj还会留在app对象上,只是它的状态变成了done()。这时候后续请求进来会直接复用这个已经结束的Future,根本不会执行新的后台逻辑。
所以判断的时候,得加上任务是否活跃的检查:
if task is None or task.done(): # 只有任务不存在或者已经结束时,才创建新任务 task = asyncio.create_task(background()) # Python3.7+推荐用create_task替代ensure_future callback = partial(done_refresh, app) task.add_done_callback(callback) app.task_obj = task
另外还要考虑并发竞态问题:如果多个请求同时进来,刚好都检测到task_obj是None,就会重复创建多个后台任务。解决这个问题很简单,给app加个异步锁就行:
# 先在app初始化的时候加个锁 app['task_lock'] = asyncio.Lock() # 然后在loading视图里用锁包裹判断逻辑 async with app['task_lock']: task = getattr(app, 'task_obj', None) if task is None or task.done(): task = asyncio.create_task(background()) callback = partial(done_refresh, app) task.add_done_callback(callback) app.task_obj = task
2. 一定要处理任务的异常
如果你的background()任务抛出了异常,默认情况下这个异常不会主动暴露出来——因为后台任务的异常如果没被捕获,只会在任务完成时默默“吃掉”,你根本不知道任务失败了。
所以在done_refresh回调里,一定要主动取出任务的结果,触发异常并处理:
def done_refresh(app, task): try: task.result() # 这里会抛出任务执行过程中出现的异常 except Exception as e: print(f"后台任务执行失败: {e}") # 可以在这里把app上的task_obj置空,方便后续重新创建任务 app.task_obj = None
要是不处理这些异常,积累多了可能会影响整个应用的稳定性。
3. 应用关闭时记得清理任务
当aiohttp应用要关闭的时候,得确保后台任务被优雅取消,不然可能会留下僵尸任务或者资源泄漏。可以给app注册一个关闭回调:
async def on_app_shutdown(app): task = getattr(app, 'task_obj', None) if task and not task.done(): task.cancel() try: await task # 等待任务被取消完成 except asyncio.CancelledError: print("应用关闭时,后台任务已被取消") # 在初始化app的时候注册这个回调 app.on_shutdown.append(on_app_shutdown)
4. 关于Application对象的本质
其实aiohttp的Application就是一个全局的、和应用生命周期绑定的存储容器,只要你保证对它的访问是协程安全的(比如用刚才说的锁),存Future或者任务对象完全没问题,非常适合存放跨请求共享的后台任务状态。
总的来说,这种做法是可行的,只要把上面这几个细节处理好,就能让后台任务稳定运行~
内容的提问来源于stack exchange,提问作者Most Wanted

