Python asyncio.create_task() - 真的需要保留参考吗?
Python asyncio.create_task() - really need to keep a reference?
asyncio.create_task()
的文档声明了以下警告:
Important: Save a reference to the result of this function, to avoid a task disappearing mid execution. (source)
我的问题是:这是真的吗?
我有几个 IO 绑定的“即发即弃”任务,我想 运行 同时使用 asyncio
通过使用 asyncio.create_task()
将它们提交到事件循环。但是,我并不真正关心协程的 return 值,或者即使它们 运行 成功,只关心它们最终 do 运行。一个用例是将来自“昂贵”计算的数据写回 Redis 数据库。如果 Redis 可用,那就太好了。如果没有,哦,好吧,没有伤害。这就是为什么我不want/need到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 asycnio.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())
输出:
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.
注释掉 await asyncio.sleep()
行时,我们从未看到 fire_and_forget_coro()
完成。这是意料之中的:当以 asyncio.run()
开始的事件循环关闭时,任务将不再执行。但似乎只要事件循环仍在 运行ning,所有任务都将得到处理,即使我从未明确创建对它们的引用。这对我来说似乎合乎逻辑,因为事件循环本身 必须 引用所有计划任务才能 运行 它们。我们甚至可以使用 asyncio.all_tasks()
!
获取它们
所以,我 认为 我可以相信 Python 对每个计划任务至少有一个强引用,只要它提交到的事件循环仍然是运行ning,因此我不必自己管理引用。但我想在这里征求第二意见。我是对的还是有我还没有意识到的陷阱?
如果我是对的,为什么文档中有明确的警告?如果您不保留对它的引用,那么通常 Python 东西会被垃圾收集。是否存在没有 运行ning 事件循环但仍有一些任务对象可供引用的情况?也许在手动创建事件循环时(从来没有这样做过)?
在 github 的 cpython 错误跟踪器上有一个关于我刚刚发现的这个主题的未解决问题:
https://github.com/python/cpython/issues/88831
引用:
asyncio will only keep weak references to alive tasks (in _all_tasks
). If a user does not keep a reference to a task and the task is not currently executing or sleeping, the user may get "Task was destroyed but it is pending!".
很遗憾,我的问题的答案是肯定的。必须保留对计划任务的引用。
但是,github 问题还描述了一个相对简单的解决方法:将所有 运行 任务保留在 set()
中,并向从 [=] 中删除自身的任务添加回调12=] 再次。
running_tasks = set()
# [...]
task = asyncio.create_task(some_background_function())
running_tasks.add(task)
task.add_done_callback(lambda t: running_tasks.remove(t))
asyncio.create_task()
的文档声明了以下警告:
Important: Save a reference to the result of this function, to avoid a task disappearing mid execution. (source)
我的问题是:这是真的吗?
我有几个 IO 绑定的“即发即弃”任务,我想 运行 同时使用 asyncio
通过使用 asyncio.create_task()
将它们提交到事件循环。但是,我并不真正关心协程的 return 值,或者即使它们 运行 成功,只关心它们最终 do 运行。一个用例是将来自“昂贵”计算的数据写回 Redis 数据库。如果 Redis 可用,那就太好了。如果没有,哦,好吧,没有伤害。这就是为什么我不want/need到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 asycnio.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())
输出:
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.
注释掉 await asyncio.sleep()
行时,我们从未看到 fire_and_forget_coro()
完成。这是意料之中的:当以 asyncio.run()
开始的事件循环关闭时,任务将不再执行。但似乎只要事件循环仍在 运行ning,所有任务都将得到处理,即使我从未明确创建对它们的引用。这对我来说似乎合乎逻辑,因为事件循环本身 必须 引用所有计划任务才能 运行 它们。我们甚至可以使用 asyncio.all_tasks()
!
所以,我 认为 我可以相信 Python 对每个计划任务至少有一个强引用,只要它提交到的事件循环仍然是运行ning,因此我不必自己管理引用。但我想在这里征求第二意见。我是对的还是有我还没有意识到的陷阱?
如果我是对的,为什么文档中有明确的警告?如果您不保留对它的引用,那么通常 Python 东西会被垃圾收集。是否存在没有 运行ning 事件循环但仍有一些任务对象可供引用的情况?也许在手动创建事件循环时(从来没有这样做过)?
在 github 的 cpython 错误跟踪器上有一个关于我刚刚发现的这个主题的未解决问题: https://github.com/python/cpython/issues/88831
引用:
asyncio will only keep weak references to alive tasks (in
_all_tasks
). If a user does not keep a reference to a task and the task is not currently executing or sleeping, the user may get "Task was destroyed but it is pending!".
很遗憾,我的问题的答案是肯定的。必须保留对计划任务的引用。
但是,github 问题还描述了一个相对简单的解决方法:将所有 运行 任务保留在 set()
中,并向从 [=] 中删除自身的任务添加回调12=] 再次。
running_tasks = set()
# [...]
task = asyncio.create_task(some_background_function())
running_tasks.add(task)
task.add_done_callback(lambda t: running_tasks.remove(t))