首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么不能直接等待python协程对象?

为什么不能直接等待python协程对象?
EN

Stack Overflow用户
提问于 2020-12-11 14:49:22
回答 3查看 176关注 0票数 1

所以我运行了一个asyncio示例:

代码语言:javascript
复制
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())

下面这段代码可以正确处理输出:

代码语言:javascript
复制
started at 14:36:06
hello
world
finished at 14:36:08

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

代码语言:javascript
复制
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())

这个结果变成了:

代码语言:javascript
复制
started at 14:37:12
hello
world
finished at 14:37:15

这花了3秒,表明协程运行不正确。

我怎样才能让后面的代码正常工作呢?或者是有什么原因导致了这种差异?

附注:该示例实际来自python文档:https://docs.python.org/3.8/library/asyncio-task.html#coroutines

EN

回答 3

Stack Overflow用户

发布于 2020-12-11 15:06:39

从docs Await expression

暂停在可等待对象上执行协程。只能在协程函数内部使用。

每当执行await时,例程都会挂起,直到等待的任务完成。在第一个示例中,两个协程都开始了,第二个中的2秒睡眠与第一个重叠。当您在第一个await之后开始运行时,第二个计时器已经过了1秒。

在第二个示例中,直到第一个await asyncio.create_task(say_after(2, 'world'))完成并且main继续运行之后,才会调度第二个and。这就是第二个任务的2秒睡眠开始的时候。

我已经结合了这些示例来显示进度。我在say_after等待之前打印了一条开始消息,在main的await之后打印了一条结束消息,而不是原始的打印结果。您可以在结果中看到时间差。

代码语言:javascript
复制
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直到第一个完成后才会被调用。

代码语言:javascript
复制
========== 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),我们可能会认为这两个重叠的异步休眠已经完成,但实际上,直到第一个允许事件循环继续的awaitsay_after才会运行。

代码语言:javascript
复制
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())

产生

代码语言:javascript
复制
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

在设置任务后向main添加一个asyncio.sleep(0),允许它们运行并执行自己的重叠睡眠,代码按我们所希望的那样工作。

代码语言:javascript
复制
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())
票数 1
EN

Stack Overflow用户

发布于 2020-12-11 15:11:17

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

代码语言:javascript
复制
await asyncio.create_task(say_after(1, 'hello'))
await asyncio.create_task(say_after(2, 'world'))

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

代码语言:javascript
复制
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())

输出:

代码语言:javascript
复制
started at 08:10:04
hello
world
finished at 08:10:06
票数 1
EN

Stack Overflow用户

发布于 2020-12-11 16:10:38

我现在有点明白这个问题了..。

await使进程在该行阻塞。

因此,在main函数中,如果你想执行parrellel任务,最好使用asyncio.wait/gather...

我认为正是Asyncio的设计风格使得以前的代码工作得很好…

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65246917

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档