
想必很多 asyncio 教程中,都介绍了一个用法:
import asyncio
async def background_task():
# do something...
...
asyncio.create_task(background_task()) asyncio 会自动后台调度运行 asyncio.Task,因此可以很方便的通过 asyncio.create_task 创建一个后台任务。但是,2022年4月25日,python/cpython 的一个不起眼的 issue (python/cpython#91887) 指出了一个问题:

https://github.com/python/cpython/issues/91887
简而言之,asyncio 仅仅会保留对 Task 的“弱引用”(weakref)。而弱引用与我们熟知的强引用(如:赋值 a=1,列表、集合等容器内包含 [1, 2], {1, 2})有一个重要的不同,就是:弱引用不会阻止对象被 Python 的垃圾回收机制回收。
也就是说,一个未完成的,甚至是正在运行的 Task,有可能被垃圾回收中断并且清除。这不仅会让你的后台任务意外终止,还有可能影响后台任务的资源回收(因为在被垃圾收集时,代码可以被运行在任意上下文中)。
为了避免这些复杂后果,我们应该做的是,不要直接使用 create_task(task()) 创建后台任务,而是使用如下方式:
# 如果只有一个 task
task = asyncio.create_task(background_task()) # 通过一个全局变量,保持对 task 的强引用
# 或者,如果有多个 task
background_tasks = set()
for i in range(10):
task = asyncio.create_task(some_coro(param=i))
# 将 task 添加到集合中,以保持强引用:
background_tasks.add(task)
# 为了防止 task 被永远保持强引用,而无法被垃圾回收
# 让每个 task 在结束后将自己从集合中移除:
task.add_done_callback(background_tasks.discard) 自此,我们保护了 Task 免遭垃圾回收的毒手,移除了一个偶发的、迷惑的 bug 隐患。
引用:
asyncio: Use strong references for free-flying tasks · Issue #91887 · python/cpython (github.com) https://github.com/python/cpython/issues/91887
Incorrect `Context` in corotine's `except` and `finally` blocks · Issue #93740 · python/cpython (github.com) https://github.com/python/cpython/issues/93740#issuecomment-1155085321
协程与任务 — Python 3.10.5 文档 https://docs.python.org/zh-cn/3/library/asyncio-task.html#asyncio.create_task
fix: prevent undone task be killed by gc by ProgramRipper · Pull Request #48 · GraiaProject/BroadcastControl (github.com) https://github.com/GraiaProject/BroadcastControl/pull/48