Python 协程基础

基础

  • 定义协程(coroutine)的两种方式:async def或使用generator实现。
    基于generator的协程应该使用@asyncio.coroutine修饰,但这并不是严格要求的,基于generator的协程使用yield from语法。

  • coroutine这个词有两种意思:

    1. 定义协程的函数
      使用async def@asyncio.coroutine。无歧义的称呼是coroutine function,isroutinefunction()返回True
    2. 调用协程函数获得的对象
      这个对象表示一个可结束的计算I/O操作。无歧义的称呼是coroutine object,iscoroutine()返回True
  • 协程可以做的事:

    • result = await futureresult = yield from future
      暂停协程,直到future完成,然后返回future的结果,或者抛出一个异常。

    • result = await coroutineresult = yield from coroutine
      等待另一个协程产生结果(或者抛出一个异常)。coroutine表达式必须为对另一个协程的调用

    • return expersssion 产生结果,结果传递给那些使用awaityield from等待此协程的协程。

    • raise exception 抛出异常。

asyncio

  • asyncio.get_event_loop

  • asyncio.ensure_future(coro_or_future, *, loop=None)
    将coroutine object包装在future中,返回Task对象。

  • BaseEventLoop.run_until_complete(future)
    阻塞,直到future完成,返回future的结果。
    如果参数是一个coroutine object,则使用ensure_future()包装一下。

本质

  1. 添加任务到事件循环中

  2. 每一次循环,从队列中取出一个任务,对其调用send,让其继续执行,将send的结果继续添加到任务队列中。如果调用send抛出了StopIteration异常,则表示任务已经完成了,则不再添加到队列中。

  3. await future,future 抛出的StopIteration由await捕获,并从StopIteration

    对象的第一参数中获取future的结果,作为await的返回值,此时被等待的future已经完成,继续执行当前coroutine之后的代码。

    如果future没有抛出异常,则future中yield的结果经由这个await(或者yield from)向上传递,一直到调用send的那个语句中(一般是事件循环中),作为send的返回值。

    所以一直都是最深层次的任务和事件循环在打交道,与途中的await没有关系,只有当最底层的future完成之后,才开始执行其上一层的任务。

疑问

  1. 第一个send(None)或者next(coro)的返回值是什么?

    第一次 send 使 generator 开始执行,直到遇到第一个 yield

    此时 generator 让出执行权,将 yield 表达式的值作为 send 的返回值,此时 generator 内部 yield 左边的赋值并没有进行,而是在这之间就 yield(让步)了。

    第二次调用 send(something), 则从 yield 赋值语句开始恢复,send 的参数作为 yield 的返回值,赋给左边的变量,继续执行。

  2. yield 与 yield from 有什么区别?

    函数中的 yield 使函数成为 generator,当 generator 到达末尾时(自然结束或遇到 return 语句),会抛出StopIteration异常。

    Python3.3 之后,return 语句的参数会成为StopIteration的 value 参数(raise StopIteration(value=something))。

    普通的由 yield 创造的 generator 一般会忽略 StopIteraion.value,也就是忽略reurn的值,只将StopIteration作为generator exhausted的条件。而yield from 会捕获StopIteraion 异常,并获取其 value 参数,作为自己的返回值。看上去没什么大不了,但却是后来await的基础。

  3. ??

    如果yield from future, future 没有 return,那么 future 中 yield 产生的值都会经过 yield from 向更高层传递,最终到达事件循环,与 yield from 一点关系也没有。当 futurn return时,yield from 很开心,终于不用做传话人了,于是获取 futurn 的结果,作为自己的返回值,也就意味着这个future resolve了,这个异步任务完成了。

    本来使用yield可以产生任意类型的值,yield from都会将其逐步传递到最顶层作为 send 的返回值。但是因为最顶层一般是事件循环,它需要将 send 的结果,继续添加到任务队列中,之后再从队列中将其取出,对其调用 send 方法(或next)让其继续执行。所以这个对象就必须有一些特殊要求,这个要求就是awaitable???? Future? Task?

  4. await与yield from的区别?

    await比yield from只多了一步参数检查:await只接受awaitable对象,包括:

    • 一个 native coroutine object returned from native coroutine function
    • generator-based coroutine object returned from a functin decoreated with asyncio.coroutine()
    • an object with an __await__ method returning an iterator.

    任何 yield from 链都是以 yield 结尾的,这是 Future 实现的基础。每个await 都是被 await链底端的 yield 暂停的。

async 与 await 语法

  • async def 函数永远是协程,即使它不包含await表达式
  • async 函数中使用 yield 或 yield from 是语法错误
  • async def 定义的是 native coroutine,设置了 CO_COROUTINE标志
  • generator-based coroutine 与 native coroutine 兼容,设置了 CO_ITERABLE_COROUTINE 标志
  • 在async def 函数外使用 await 是语法错误
  • 为 await 传递非awaitable对象会报 TypeError

参考链接