And*_*ndy 17 python task python-3.x python-asyncio
的文档asyncio.create_task()指出以下警告:
重要提示:保存对此函数结果的引用,以避免任务在执行过程中消失。(来源)
我的问题是:这是真的吗?
我有几个 IO 绑定的“即发即忘”任务,我想asyncio通过使用将它们提交到事件循环来同时运行这些任务asyncio.create_task()。但是,我并不真正关心协程的返回值,或者即使它们运行成功,只关心它们最终会运行。一种用例是将“昂贵”计算中的数据写回 Redis 数据库。如果 Redis 可用,那就太好了。如果没有,哦,好吧,没有坏处。这就是为什么我不想/不需要await这些任务。
这是一个通用示例:
import asyncio
async def fire_and_forget_coro():
"""Some random coroutine waiting for IO to complete."""
print('in fire_and_forget_coro()')
await asyncio.sleep(1.0)
print('fire_and_forget_coro() done')
async def async_main():
"""Main entry point of asyncio application."""
print('in async_main()')
n = 3
for _ in range(n):
# create_task() does not block, returns immediately.
# Note: We do NOT save a reference to the submitted task here!
asyncio.create_task(fire_and_forget_coro(), name='fire_and_forget_coro')
print('awaiting sleep in async_main()')
await asyncio.sleep(2.0) # <-- note this line
print('sleeping done in async_main()')
print('async_main() done.')
# all references of tasks we *might* have go out of scope when returning from this coroutine!
return
if __name__ == '__main__':
asyncio.run(async_main())
Run Code Online (Sandbox Code Playgroud)
输出:
in async_main()
awaiting sleep in async_main()
in fire_and_forget_coro()
in fire_and_forget_coro()
in fire_and_forget_coro()
fire_and_forget_coro() done
fire_and_forget_coro() done
fire_and_forget_coro() done
sleeping done in async_main()
async_main() done.
Run Code Online (Sandbox Code Playgroud)
当注释掉该await asyncio.sleep()行时,我们永远看不到fire_and_forget_coro()完成。这是可以预料的:当事件循环以asyncio.run()close 开始时,任务将不再执行。但看来只要事件循环仍在运行,所有任务都会被处理,即使我从未明确创建对它们的引用。这对我来说似乎很合乎逻辑,因为事件循环本身必须引用所有计划任务才能运行它们。我们甚至可以让它们全部使用asyncio.all_tasks()!
因此,我认为只要提交到的事件循环仍在运行,我就可以相信 Python 对每个计划任务至少有一个强引用,因此我不必自己管理引用。但我想在这里得到第二个意见。我是对的还是有我还没有认识到的陷阱?
如果我是对的,为什么文档中会有明确的警告?如果你不保留对某个东西的引用,它就会被垃圾回收,这是 Python 的一个常见现象。是否存在没有正在运行的事件循环但仍有一些任务对象可供引用的情况?也许在手动创建事件循环时(从未这样做过)?
And*_*ndy 15
github 上的 cpython bug tracker 有一个关于我刚刚发现的主题的未解决问题: https ://github.com/python/cpython/issues/88831
引用:
asyncio 只会保留对活动任务的弱引用(在 中
_all_tasks)。如果用户没有保留对任务的引用并且该任务当前没有执行或休眠,则用户可能会收到“任务已被销毁,但它正在挂起!”。
不幸的是,我的问题的答案是肯定的。人们必须保留对计划任务的引用。
然而,github问题还描述了一个相对简单的解决方法:将所有正在运行的任务保留在a中,set()并向任务添加回调,以将其自身从a中删除set()。
running_tasks = set()
# [...]
task = asyncio.create_task(some_background_function())
running_tasks.add(task)
task.add_done_callback(lambda t: running_tasks.remove(t))
Run Code Online (Sandbox Code Playgroud)