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))