【问题标题】:python asyncios create_task and await functionspython asyncios create_task 和 await 函数
【发布时间】:2018-10-10 10:05:28
【问题描述】:

我正在尝试理解 pythons asynico 模块,并在 https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task 遇到以下代码

import time
import asyncio

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

    print('started at', time.strftime('%X'))

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    await task2

    print('finished at', time.strftime('%X'))

asyncio.run(main())

事实证明,await task2(或task1,但不能同时删除)可以简单地删除,并且代码看起来完全一样。我觉得这很违反直觉,这里发生了什么? 感谢您的宝贵时间。

【问题讨论】:

  • @BradSolomon,很有趣,但对我来说,如果 await task2 被注释掉,两个任务都会运行,我不会得到任何取消。我在 Windows 上使用 python 3.7
  • 我很困惑,一个await 可以导致两个任务都运行。

标签: python asynchronous


【解决方案1】:

您提出了三种不同的场景:

  1. 没有await 语句(注释掉两个)
  2. 仅使用await task1(注释掉第二个)
  3. 仅使用await task2(注释掉第一个)

这是你的脚本;将task2 的睡眠时间延长一点只是为了说明。

# tasktest.py
import time
import asyncio

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(3, 'world'))

    print('started at', time.strftime('%X'))
    await task1
    # await task2
    print('finished at', time.strftime('%X'))

asyncio.run(main())

1。没有await 语句

这是asyncio.run()的肉:

loop = events.new_event_loop()
try:
    events.set_event_loop(loop)
    loop.set_debug(debug)
    return loop.run_until_complete(main)   # < -----
finally:
    try:
        _cancel_all_tasks(loop)            # < -----
        loop.run_until_complete(loop.shutdown_asyncgens())
    finally:
        events.set_event_loop(None)
        loop.close()

重要的是,循环关心main() 是否完成,然后取消与正在运行的事件循环关联的所有其他任务。 (每个任务在指定时都是tied to一个事件循环。)

如果您在没有任何 await 语句的情况下定义 main()create_task() 会安排要执行的任务,但 main() 不会等待其中任何一个完成。

2。 await task1

设置:

await task1
# await task2

输出:

(base_py37) $ python3 tasktest.py 
started at 11:06:46
hello
finished at 11:06:47

两个任务都从挂起转为运行,但只有 task1 完成,因为 main() 只等待大约 1 秒的任务,不足以让 task2 运行。* (注意main() 只需 1 秒。)

3。 await task2

设置:

# await task1
await task2

输出:

(base_py37) $ python3 tasktest.py 
started at 11:08:37
hello
world
finished at 11:08:40

两个任务都从挂起变为正在运行,现在 task1task2 都完成了,因为 main() 等待一个需要大约 3 秒的任务,时间足以让两个任务运行到完成。


*这至少适用于我的设置(Mac OSX,...),但正如此处的另一个答案所述,在另一个设置中,时间可能会有所不同,并且如果任务运行时间相似,两者都可能在案例 # 2 之类的地方运行。

【讨论】:

    【解决方案2】:

    await 不会启动任务中的协程,它只是告诉协程 main 为这两个 coros 等待。事件循环以 run_until_complete 隐式启动,这反过来意味着它将等待传递给它的 coro (main) 完成。由 coro 来延长自己的生命周期(通过awaiting)足够长,以确保从它内部创建的任务能够完成。

    async def main():
        task1 = asyncio.create_task(
            say_after(1, 'hello'))
        task2 = asyncio.create_task(
            say_after(2, 'world'))
        #await task1
        #await task2
        print(asyncio.all_tasks(asyncio.get_event_loop()))
    
    # will print (added line breaks and shortened paths for readibility):
    {
        <Task pending coro=<main() running at C:/Users/.../lmain.py:17> cb=[_run_until_complete_cb() at C:...\lib\asyncio\base_events.py:150]>, 
        <Task pending coro=<say_after() running at C:/Users/.../lmain.py:5>>, 
        <Task pending coro=<say_after() running at C:/Users/.../lmain.py:5>>
    }
    

    如您所见,所有三个 coros 都在没有 await 的情况下运行。只是这两个say_after coros 会比隐式控制事件循环运行多长时间的main 花费更长的时间。

    如果您让main 等待循环中完成的工作足够长的时间,那么这两个任务都会完成:

    async def main():
        task1 = asyncio.create_task(
            say_after(1, 'hello'))
        task2 = asyncio.create_task(
            say_after(2, 'world'))
        print('started at', time.strftime('%X'))
        #await task1
        #await task2
        await asyncio.sleep(5)
        print('finished at', time.strftime('%X'))
    
    # output
    started at 15:31:48
    hello
    world
    finished at 15:31:53
    

    所以,当您测试注释掉上面的task1 和/或task2awaiting 时,哪个任务(如果有)完成基本上只是时间问题,主要受硬件、操作系统和可能的运行时影响(即 IDE 与 shell)。

    附:任务只有three statespendingcancelledfinished。每个任务在创建后立即处于状态 pending 并保持该状态,直到包含在其中的协程终止(以任何方式)或直到它被控制它的事件循环取消。

    【讨论】:

    • @BradSolomon 是的,但公平地说,asyncio.runasyncio.create_task 的文档声明这些功能目前是临时的。希望如果决定保留该界面,文档将得到改进以强调这一点。
    猜你喜欢
    • 1970-01-01
    • 2012-09-27
    • 1970-01-01
    • 2018-01-19
    • 2019-02-17
    • 2020-09-22
    • 1970-01-01
    • 2021-03-10
    • 2021-08-06
    相关资源
    最近更新 更多