【问题标题】:Python asyncio.create_task() - really need to keep a reference?Python asyncio.create_task() - 真的需要保留参考吗?
【发布时间】:2022-11-06 22:30:22
【问题描述】:

asyncio.create_task() 的文档指出以下警告:

重要的:保存对该函数结果的引用,以避免任务在执行过程中消失。 (source)

我的问题是:这是真的吗?

我有几个 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 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() 开始的事件循环关闭时,将不再执行任务。但似乎只要事件循环仍在运行,所有任务都会得到处理,即使我从未明确创建对它们的引用。这对我来说似乎是合乎逻辑的,因为事件循环本身必须参考所有计划任务以运行它们。我们甚至可以使用asyncio.all_tasks() 获取它们!

所以我思考只要提交给它的事件循环仍在运行,我就可以相信 Python 对每个计划任务至少有一个强引用,因此我不必自己管理引用。但我想在这里发表第二意见。我是对的还是有我还没有认识到的陷阱?

如果我是对的,为什么文档中有明确的警告?如果你不保留对它的引用,那么通常的 Python 事情就是垃圾收集。是否存在没有正在运行的事件循环但仍有一些任务对象要引用的情况?也许在手动创建事件循环时(从未这样做过)?

【问题讨论】:

  • await asycnio.sleep(2.0) asyncio 错字

标签: python python-asyncio


【解决方案1】:

在 github 的 cpython bug tracker 上有一个关于我刚刚发现的这个主题的未解决问题: https://github.com/python/cpython/issues/88831

引用:

asyncio 只会保留对活动任务的弱引用(在_all_tasks 中)。如果用户没有保留对任务的引用并且该任务当前没有执行或休眠,则用户可能会收到“任务已销毁但它处于挂起状态!”。

所以很遗憾,我的问题的答案是肯定的。必须保留对计划任务的引用。

但是,github 问题还描述了一个相对简单的解决方法:将所有正在运行的任务保存在 set() 中,并为该任务添加一个回调,使其再次从 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))

【讨论】:

    【解决方案2】:

    在 python3.11 中,有一个新的 API asyncio.TaskGroup.create_task
    它会做the other answer 提到的事情,所以你不需要自己做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-14
      相关资源
      最近更新 更多