为什么你不能直接等待python coroutine对象?

hm2xizp9  于 2023-10-21  发布在  Python
关注(0)|答案(3)|浏览(183)

所以我运行一个asyncio示例:

import asyncio, time

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(f"started at {time.strftime('%X')}")

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

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

这段代码与输出正确工作:

started at 14:36:06
hello
world
finished at 14:36:08

2个协程异步运行,最后花了2秒,没有问题。然而,当我合并将这些行组合在一起并直接等待Task对象时,如下所示:

import asyncio, time

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

async def main():

    print(f"started at {time.strftime('%X')}")

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await asyncio.create_task(say_after(1, 'hello'))
    await asyncio.create_task(say_after(2, 'world'))

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

该结果变为:

started at 14:37:12
hello
world
finished at 14:37:15

这花费了3秒钟,表明协程运行不正确。
我怎样才能使后面的代码正常工作?还是有什么东西导致了这种差异?
P.S.这个例子实际上来自python doc:https://docs.python.org/3.8/library/asyncio-task.html#coroutines

bwntbbo3

bwntbbo31#

await使代码在等待的协程完成后“停止”并继续,因此当您编写

await asyncio.create_task(say_after(1, 'hello'))
await asyncio.create_task(say_after(2, 'world'))

第二个任务是在第一个协同程序完成后创建和运行的,因此总共需要3秒。作为一种解决方案,考虑使用像gatherwait这样的函数。举例来说:

import asyncio, time
    
    async def say_after(delay, what):
        await asyncio.sleep(delay)
        print(what)
    
    async def main():
        print(f"started at {time.strftime('%X')}")

        # Wait until both tasks are completed (should take
        # around 2 seconds.)
        await asyncio.gather(say_after(1, 'hello'), say_after(2, 'world'))

        print(f"finished at {time.strftime('%X')}")

    asyncio.run(main())

输出量:

started at 08:10:04
hello
world
finished at 08:10:06
m3eecexj

m3eecexj2#

从docs Await表达式:
暂停对可等待对象执行协程。只能在协程函数中使用。
无论何时执行await,例程都会挂起,直到等待的任务完成。在第一个例子中,两个协程都开始,并且第二个协程中的2秒睡眠与第一个相重叠。当您在第一个await之后开始运行时,第二个计时器中已经过了1秒。
在第二个示例中,第二个await asyncio.create_task(say_after(2, 'world'))直到第一个完成并且main继续运行之后才被调度。这是第二个任务的2秒睡眠开始的时候。
我结合了这些例子来展示进展。我没有打印原始的输出,而是在say_after等待之前打印一条开始消息,在main的await之后打印一条结束消息。你可以在结果中看到时间差。

import asyncio, time

async def say_after(delay, what):
    print(f"start {what} at {time.strftime('%X')}")
    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'))
    await task1
    print(f"Finished hello at {time.strftime('%X')}")
    await task2
    print(f"Finished world at {time.strftime('%X')}")

async def main2():
    await asyncio.create_task(say_after(1, 'hello'))
    print(f"Finished hello at {time.strftime('%X')}")
    await asyncio.create_task(say_after(2, 'world'))
    print(f"Finished world at {time.strftime('%X')}")

print("========== Test 1 ============")
asyncio.run(main())

print("========== Test 2 ============")
asyncio.run(main2())

第二个测试的结果表明,第二个say_after直到第一个say_after完成才被调用。

========== Test 1 ============
start hello at 00:51:42
start world at 00:51:42
hello
Finished hello at 00:51:43
world
Finished world at 00:51:44
========== Test 2 ============
start hello at 00:51:44
hello
Finished hello at 00:51:45
start world at 00:51:45
world
Finished world at 00:51:47

main中,创建了运行asyncio.sleep的任务,但这些任务直到main返回even循环时才实际运行。如果我们添加一个time.sleep(3),我们可能会期望这两个重叠的sleep已经完成,但实际上say_after甚至没有运行,直到第一个await让事件循环继续。

import asyncio, time

async def say_after(delay, what):
    print(f"starting {what} at {time.time()-start}")
    await asyncio.sleep(delay)
    print(what)

async def main():
    global start
    print('time asyncio.sleep with intermedite time.sleep')
    start = time.time()
    task1 = asyncio.create_task(say_after(1, 'hello'))
    task2 = asyncio.create_task(say_after(2, 'world'))

    # similate working for 3 seconds with non asyncio sleep
    time.sleep(3)
    print(f'expect 3 got {time.time()-start}')
    await task1  # <== where the 2 `say_after` tasks start
    print(f'expect 3 got {time.time()-start}')
    await task2
    print(f'expect 3 got {time.time()-start}')

asyncio.run(main())

产生

time asyncio.sleep with intermedite time.sleep
expect 3 got 3.0034446716308594
starting hello at 3.003699541091919
starting world at 3.0038907527923584
hello
expect 3 got 4.005880355834961
world
expect 3 got 5.00671124458313

在设置任务后将asyncio.sleep(0)添加到main,允许它们运行并执行自己的重叠睡眠,代码可以按照我们的要求工作。

import asyncio, time

async def say_after(delay, what):
    print(f"starting {what} at {time.time()-start}")
    await asyncio.sleep(delay)
    print(what)

async def main():
    global start
    print('time asyncio.sleep with event loop poll and intermedite time.sleep')
    start = time.time()
    task1 = asyncio.create_task(say_after(1, 'hello'))
    task2 = asyncio.create_task(say_after(2, 'world'))

    # let the `say_after` tasks (and anything else pending) run
    await asyncio.sleep(0)

    # similate working for 3 seconds with non asyncio sleep
    time.sleep(3)
    print(f'expect 3 got {time.time()-start}')
    await task1  # <== where the 2 `say_after` tasks start
    print(f'expect 3 got {time.time()-start}')
    await task2
    print(f'expect 3 got {time.time()-start}')

asyncio.run(main())
wrrgggsh

wrrgggsh3#

我现在有点明白问题了...
await使进程阻塞在该行。
所以在main函数中,如果你想执行并行任务,最好使用asyncio.wait/gather...
我认为这只是Asyncio的设计风格使以前的代码工作得很好...

相关问题