不死心,再来问一遍关于 Python 的 asyncio 问题

2019-07-22 17:42:42 +08:00
 waibunleung

有两段代码,是关于 asyncio 的。
代码段一:

import asyncio

async def worker_1():
    print('worker_1 start')
    await asyncio.sleep(1)
    print('worker_1 done')

async def worker_2():
    print('worker_2 start')
    await asyncio.sleep(2)
    print('worker_2 done')

async def main():
    print('before await')
    await worker_1()
    print('awaited worker_1')
    await worker_2()
    print('awaited worker_2')

%time asyncio.run(main())

########## 输出 ##########

before await
worker_1 start
worker_1 done
awaited worker_1
worker_2 start
worker_2 done
awaited worker_2
Wall time: 3 s

代码段二:

import asyncio

async def worker_1():
    print('worker_1 start')
    await asyncio.sleep(1)
    print('worker_1 done')

async def worker_2():
    print('worker_2 start')
    await asyncio.sleep(2)
    print('worker_2 done')

async def main():
    task1 = asyncio.create_task(worker_1())
    task2 = asyncio.create_task(worker_2())
    print('before await')
    await task1
    print('awaited worker_1')
    await task2
    print('awaited worker_2')

%time asyncio.run(main())

########## 输出 ##########

before await
worker_1 start
worker_2 start
worker_1 done
awaited worker_1
worker_2 done
awaited worker_2
Wall time: 2.01 s

问题:代码段一里面的协程(coroutine)换成代码段二的任务(task)后,为什么执行顺序就变了?这个过程中发生了什么事情?

说说我的猜想:
发现调用 asyncio.run(main()) 或者 [asyncio.gather()->asyncio.get_event_loop()->loop.run_until_complete()]都会将一个 coroutine 转化成 task/future 再放 event loop 里面去, 交由 event loop 去管理这些 task/future。代码段一只将 main()这个 coroutine 封装成了 task 加入到 event loop 中,所以整个 event loop 中只有一个 task 在走,在这个 task 中代码是顺序执行的,所以最后呈现出同步执行的结果;
但是代码段二调用了两次 asyncio.create_task(),这个方法会将一个 coroutine 转换成一个 task 并且放到 event loop 中,所以整个 event loop 其实有三个 task ( main,task1,task2 ),之后程序就交给 event loop 来调度,执行顺序就变不同了。 这个假设目前来看好像能解释得通

最后,希望各位能指点一下~

6494 次点击
所在节点    Python
90 条回复
waibunleung
2019-07-22 18:32:05 +08:00
@so1n 我还是同样的问题,我能看到注释说 Schedule the execution of a coroutine object in a spawn task.
Return a Task object.
问题是 Schedule the execution of a coroutine object in a spawn task.这个 Schedule 在哪里体现的?
dbow
2019-07-22 18:32:13 +08:00
await 协程时, 协程才会开始执行, 执行返成结果返回给 await
create_task 时会先直接 await 协程, 相当于 task = await 协程; await task, 由于你两个 create_task 是连在一起的, 它们就没有顺序关系, 直接开始交错执行了。

看官方文档, https://docs.python.org/zh-cn/3/library/asyncio-task.html
asyncio.create_task(coro)
将 coro 协程 打包为一个 Task 排入日程准备执行。返回 Task 对象。

该任务会在 get_running_loop() 返回的循环中执行,如果当前线程没有在运行的循环则会引发 RuntimeError。
waibunleung
2019-07-22 18:33:00 +08:00
@guokeke 所以再 task 初始化的时候,那个__init__函数你看了吗?就几行代码...
guokeke
2019-07-22 18:35:16 +08:00
@waibunleung
```
self._loop.call_soon(self._step)
self.__class__._all_tasks.add(self)
```
waibunleung
2019-07-22 18:35:36 +08:00
@BBCCBB 按照你的说法,async 函数也会被转换成 task 而放到 eventloop 中,那其实代码一和代码二的 async 函数最后都会变成 task 才被加进 event loop,那为什么代码一是顺序的,代码二不是?
so1n
2019-07-22 18:36:57 +08:00
waibunleung
2019-07-22 18:37:01 +08:00
@guokeke 可以把你的查看路径发一下给我吗?从哪个函数跳到哪个函数这样的路径
BBCCBB
2019-07-22 18:38:25 +08:00
因为你代码 1 里调用 await 一个协程函数的时候他才加进去啊,又不是你写了 await, 在开始运行的时候就加进去的。。。你 await worker2 的时候, 你 await 的 worker1 早就执行完了。毕竟你 await worker2 的顺序在 await worker1 之后。
dbow
2019-07-22 18:39:13 +08:00
协程的启动
async def print_msg(msg):
print(msg)

coro = print_msg()
coro.send(None) 执行至等待处(await)
coro.send(xxx) 恢复执行, xx 作为函数里面 await 的结果
一直 send, 一直执行, 直到 StopIteration,协程执行完毕。

create_task 时直接 send(None) 启动了协程执行, 导致两个 task 没有顺序关系。
BBCCBB
2019-07-22 18:39:28 +08:00
建议你先仔细看看官方文档。不然你理解起来很费劲。我们解释起来也费劲。
waibunleung
2019-07-22 18:41:35 +08:00
@BBCCBB 我已经看了不少资料了...你看看我上个问题,你肯定又没仔细看,当然会费劲...
janxin
2019-07-22 18:42:18 +08:00
第一个 await 过程因为是阻塞等待结果的,其实等同于不包含 async/await 的同步写法
so1n
2019-07-22 18:43:17 +08:00
waibunleung
2019-07-22 18:44:00 +08:00
@so1n 我用的 pycharm 查看的源码,但是不知道它为什么跳不到你们说的 call_soon 的函数那里去,我点 create_task 之后,跳去了 task.pyi 而不是 task.py ,task.pyi 的__init__方法只有一行...是我看得有问题吗。。。
dbow
2019-07-22 18:44:22 +08:00
create_task 创建了一个 task , task 构造函数里, 使用了 call_soon, call_soon 的意思 event loop 在下一批调度时立刻执行这个 task, 不再等待.
dbow
2019-07-22 18:45:31 +08:00
waibunleung
2019-07-22 18:46:27 +08:00
@dbow 看不到你的图片...
so1n
2019-07-22 18:48:10 +08:00
@waibunleung pycharm 跳进去的不一定是正确的, 特别是当这个函数是用实现时,pycharm 只会留下一个文档和一句 pass
dbow
2019-07-22 18:48:19 +08:00
meik2333
2019-07-22 18:49:00 +08:00
换个思路来看,你 await worker_1() 的时候,Python 解释器根本不知道你待会儿还要 await worker_2(),就更无从谈起两个并行执行了。只有等你 await worker_1() 结束后,才能发现还要 await worker_2()。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/585164

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX