【问题标题】:How to wait for all tasks to finish before terminating the event loop?如何在终止事件循环之前等待所有任务完成?
【发布时间】:2021-10-31 16:13:39
【问题描述】:

Python 中确保所有并发任务在事件循环结束之前完成的标准方法是什么?这是一个简化的示例:

import asyncio

async def foo(delay):
    print("Start foo.") # Eg: Send message
    asyncio.create_task(bar(delay))
    print("End foo.")

async def bar(delay):
    print("Start bar.")
    await asyncio.sleep(delay)
    print("End bar.") # Eg: Delete message after delay

def main():
    asyncio.run(foo(2))

if __name__ == "__main__":
    main()

当前输出:

Start foo. # Eg: Send message
End foo.
Start bar.

期望的输出:

Start foo. # Eg: Send message
End foo.
Start bar.
End bar. # Eg: Delete message after delay

我尝试在loop.run_until_complete() 之后运行所有未完成的任务,但这不起作用,因为到那时循环将被终止。我还尝试将 main 函数修改为以下内容:

async def main():
    await foo(2)

    tasks = asyncio.all_tasks()
    if len(tasks) > 0:
        await asyncio.wait(tasks)

if __name__ == "__main__":
    asyncio.run(main())

输出是正确的,但它永远不会终止,因为协程 main() 是其中一项任务。上面的设置也是discord.py发送消息,一段时间后删除,只是改用loop.run_forever(),所以没有遇到问题。

【问题讨论】:

  • 在你的 foo 函数中尝试:task = [asyncio.create_task(bar(delay))] 然后await asyncio.gather(*task, return_exceptions=False)
  • 请注意,这些天您不应该直接与循环交互。使用asyncio.run 而不是手动获取和启动循环。
  • @user56700 我试过了,但它会锁定 foo 直到栏完成。 bar 必须同时运行。一个例子是一个机器人发送一个过期的消息。发送消息的函数应该尽快运行并返回(调用函数只想等到消息发送,而不是等到消息过期)。
  • @MisterMiyagi 谢谢,我用改进的代码编辑了帖子。

标签: python python-3.x python-asyncio


【解决方案1】:

asyncio(和类似的框架)中没有等待所有任务的标准方法,实际上不应该尝试。就线程而言,Task 表示 both regular and daemon activities。不加选择地等待所有任务可能会导致应用程序无限期停止。

创建但从未awaited 的任务实际上是后台/守护程序任务。相反,如果一个任务不应该被视为后台/守护进程,那么调用者有责任确保它是awaited。


最简单的解决方案是让每个协程 await 和/或取消它产生的所有任务。

async def foo(delay):
    print("Start foo.")
    task = asyncio.create_task(bar(delay))
    print("End foo.")
    await task  # foo is done here, it ensures the other task finishes as well

由于async/tasks 的全部意义在于进行廉价的任务切换,因此这是一种廉价的操作。它也不应该影响任何精心设计的应用程序:

  • 如果函数的目的是产生一个值,则任何子任务都应该是产生该值的一部分。
  • 如果函数的目的是某种副作用,则任何子任务都应该是该副作用的一部分。

对于更复杂的情况,返回任何未完成的任务是值得的。

async def foo(delay):
    print("Start foo.")
    task = asyncio.create_task(bar(delay))
    print("End foo.")
    return task  # allow the caller to wait for our child tasks

这需要调用者明确地处理未完成的任务,但要给予及时的回复和最大的控制权。然后顶级任务负责处理任何孤立任务。

对于一般的async 编程,structured programming paradigm 在管理对象中编码了“处理未完成任务”的概念。在 Python 中,这种模式已被the trio library 编码为所谓的Nursery 对象。

import trio

async def foo(delay, nursery):
    print("Start foo.")
    # spawning a task via a nursery means *someone* awaits it
    nursery.start_soon(bar, delay)
    print("End foo.")

async def bar(delay):
    print("Start bar.")
    await trio.sleep(delay)
    print("End bar.")

async def main():
    # a task may spawn a nursery and pass it to child tasks
    async with trio.open_nursery() as nursery:
        await foo(2, nursery)

if __name__ == "__main__":
    trio.run(main)

虽然已建议 asyncio as TaskGroups, so far it has been deferred 使用此模式。 不过,asyncio 模式的各种端口可通过第三方库获得。

【讨论】:

    猜你喜欢
    • 2016-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-09
    • 1970-01-01
    • 2011-03-17
    相关资源
    最近更新 更多