为什么只有基于生成器的协程可以真正的暂停执行并强制性返回给事件循环? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
NoAnyLove
V2EX    Python

为什么只有基于生成器的协程可以真正的暂停执行并强制性返回给事件循环?

  •  
  •   NoAnyLove 2017-06-18 13:35:23 +08:00 9365 次点击
    这是一个创建于 3066 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在 Python 核心开发人员 Brett Cannon 的一篇文章《 How the heck does async/await work in Python 3.5?》中提到:

    One very key point I want to make about the difference between a generator-based coroutine and an async one is that only generator-based coroutines can actually pause execution and force something to be sent down to the event loop.

    之后总结中还有一句:

    You can only make a coroutine call chain pause with a generator-based coroutine.

    对应的翻译版在这里:《[译] Python 3.5 协程究竟是个啥》,引用如下:

    关于基于生成器的协程和 async 定义的协程之间的差异,我想说明的关键点是只有基于生成器的协程可以真正的暂停执行并强制性返回给事件循环。

    你只能通过基于生成器的定义来实现协程的暂停。

    在我的理解中,基于生成器的协程使用 yield from 语句,async 定义的协程使用 await 语句,虽然两者可以接受的对象不同(具体原文中有详细描述),但是两者的作用应该是一样的啊:都是暂停当前协程的执行,转交出执行权,直到 yield from 或者 await 的对象执行完成后再返回,继续执行后面的语句。

    对此,PEP-492 也有提,下面是其中提到的例子:

    async def read_data(db): data = await db.fetch('SELECT ...') ... 

    await , similarly to yield from , suspends execution of read_data coroutine until db.fetch awaitable completes and returns the result data.

    It uses the yield from implementation with an extra step of validating its argument.

    也就是说,await应该使用了yield from类似的实现,作用也是暂停当前执行流程。那么,为啥await不能“真正的暂停执行并强制性返回给事件循环”?

    一个脑洞:难道是因为await将执行权转交给了后面的对象,但是并没有转交给作为调度者的消息循环?

    7 条回复    2017-06-21 10:47:19 +08:00
    shyling
        1
    shyling  
       2017-06-18 15:55:48 +08:00
    You can only make a coroutine call chain pause with a *generator-based* coroutine.

    难道 async function 不是 generator-based 么?
    NoAnyLove
        2
    NoAnyLove  
    OP
       2017-06-18 23:12:00 +08:00
    @shyling 以`async def`声明的函数算作 native coroutine,算 generator-based 的协程
    gnaggnoyil
        4
    gnaggnoyil  
       2017-06-19 08:07:12 +08:00
    个人理解是因为 yield from 可以 yield 一个 Future,所以配合 event loop 的时候可以用于 sleep.其它的 generator 在 coroutine 切换上下文的之后都是继续执行当前上下文的语句的.

    而 Future 只能被 yield from,不能被 await.
    NoAnyLove
        5
    NoAnyLove  
    OP
       2017-06-19 13:13:21 +08:00
    @shyling accepted 的那个答案,投票为 0,明显是错误的,两者并不是完全没有区别。另一个比较可靠一点的说明在这里<https://stackoverflow.com/questions/40571786/asyncio-coroutine-vs-async-def>

    @gnaggnoyil `asyncio.Future`可以用于`await`语句,它有实现`__await__()`方法。

    我用 pudb 调试《 How the heck does async/await work in Python 3.5?》中最后给出的那个 Example。从代码的运行流程来看,Example 中 sleep 协程的`actual = yield wait_until`语句有点像 Exception 的感觉,step in 会跳回到`waited = await sleep(1)`,再一次 step in,就会像 Exception 没有被处理继续 propagate 一样,跳到了事件循环`run_until_complete`函数中。

    感觉还有点混乱。
    keakon
        6
    keakon  
       2017-06-20 11:36:56 +08:00
    这个问题看着挺绕的,但是道理却很简单。

    假如要用 native coroutine 来实现「 pause execution and force something to be sent down to the event loop 」,那么你写的代码大概如下:
    async def native_coroutine(): # ...
    async def f(): await native_coroutine()

    再来看 native_coroutine 的函数体,你有如下选择:
    1. await 一个 awaitable 对象:这会导致你需要再定义一个 native coroutine,递归回到前面的选择。
    2. return 一个值:这会导致代码变成同步的,f 函数会立刻接收到 StopIteration 异常。
    3. raise 一个异常:这会导致代码变成同步的,f 函数会立刻接收到异常。
    4. yield 一个值:在 Python 3.5 或之前是语法错误;在 3.6 或之后它变成了 asynchronous generator (见 PEP 525 ),也不能用在 await 表达式里(它不是 awaitable,没有定义 __await__ 方法,会抛出 TypeError: object async_generator can't be used in 'await' expression )。

    对于选择 1,你当然也可以自定义一个 awaitable:
    class Awaitable(object):
    ----def __init__(self, count):
    --------self.count = count
    ----def __await__(self):
    --------yield from range(self.count)
    这样你就不是用 generator based coroutine 来暂停执行了,但这也不是 native coroutine 了。

    可见这完全是语言的限制,因为 yield 后面可以跟任意的值,yield from 可以接任意 generator 对象,而 await 却只能接 awaitable 对象。

    所以纠结 native coroutine 为什么不能「暂停执行并强制性返回给事件循环」没多大意义,因为你实际在编写最底层调用的那句代码时,肯定要用 yield 或 yield from。但如果不是编写框架,你基本上只需要写到 await native_coroutine 这层的代码。
    NoAnyLove
        7
    NoAnyLove  
    OP
       2017-06-21 10:47:19 +08:00
    @keakon 非常干感谢你的回复,我反复读了很多遍,感觉明白了很多。我本来纠结的是功能上为啥需要 generator-based coroutine,不过你从逻辑上说明了为啥需要 generator-based coroutine,让我也有种豁然开朗的感觉。

    《 How the heck does async/await work in Python 3.5?》也提到如果只是和事件循环打交道,其实不用关心这些细节,因为框架 API 会帮助我们处理这些细节。不过我刚好对为啥不能通过 async 定义的协程来实现 asyncio.sleep()比较好奇,所以想研究一下。

    我的感觉上是,最底层的协程,必须要通过`yield`或者`yield from`语句,将执行流程转交给处于调用最顶层的事件循环。从调试 step in 跟进的结果来看,await 语句不会捕获`yield`或者`yield from`返回的值,这个值会被传递给事件循环,也就是调用`coro.send(None)`的地方。如果最底层的函数也是通过`async def`定义的,那么将没有办法把执行流程转移给事件循环。

    考虑到“暂停并强制性返回给事件循环”,我想到其实还可以抛出异常,因为异常如果没有捕获就会向上传递,可以被事件循环捕获到。结果试了一下不行,才想起异常抛出后,虽然会向`yield`或者`yield from`一样暂停当前执行,但是不会从抛出异常的地方继续自行,Orz
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5994 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 35ms UTC 02:21 PVG 10:21 LAX 18:21 JFK 21:21
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86