接下来,我们会选择基于生成器的协程对象。基本上一个 Python 的生成器(例如:某个有yield表达式的函数)可以通过 types.coroutine 装饰被标记成一个协程。所以,这是一个最简单的例子:
@types.coroutine
def switch():
yield
这定义了一个基于生成器的协程函数。要得到基于生成器的协程对象,只需要执行这个函数。我们可以把我们的 coro1 协程修改成下面这样:
async def coro1():
print("C1: Start")
await switch()
print("C1: Stop")
通过上面的修改,我们期望 coro1 和 coro2 可以交错执行。到目前为止,输出是这样的:
C1: Start
C2: Start
C2: a
C2: b
C2: c
C2: Stop
我没看到正如期望的,在第一条打印语句之后,coro1 停止执行,coro2 接着执行。实际上,我们可以通过下面的代码查看协程对象是如何暂停执行的:
print("c1 suspended at: {}:{}".format(c1.gi_frame.f_code.co_filename, c1.gi_frame.f_lineno))
这可以打印 await 表达式所在的行。(注意:打印的是最外层的 await,所以这里只是起示例作用,通常情况下用处不大)。
现在的问题是,如何让 coro1 继续执行完呢?我们可以再调用一次 send,代码如下:
try:
c1.send(None)
except StopIteration:
pass
try:
c2.send(None)
except StopIteration:
pass
try:
c1.send(None)
except StopIteration:
pass
得到的输出跟预期一样:
C1: Start
C2: Start
C2: a
C2: b
C2: c
C2: Stop
C1: Stop
目前,我们通过为不同的协程显式调用 send来让它们都执行结束。通常情况下这种方式不是很好。我们希望的是有一个函数来控制所有的协程的运行,直到全部协程都执行完成。换句话说,我们期望连续不断的调用 send,驱动不同的协程去执行,直到send抛出 StopIteration 异常。
为此我们新建一个函数,这个函数传入一个协程列表,函数执行这些协程直到全部结束。我们现在要做的就是调用这个函数。
def run(coros):
coros = list(coros)
while coros:
# Duplicate list for iteration so we can remove from original list.
for coro in list(coros):
try:
coro.send(None)
except StopIteration:
coros.remove(coro)
这段代码每次从协程列表里取一个协程执行,如果捕获到 StopIteration 异常,就把这个协程从队列里去掉。
接下来我们把手工调用 send 的代码去掉,代码如下:
c1 = coro1()
c2 = coro2()
run([c1, c2])
综上所述,在 Python 3.5,我们现在可以通过新的 await 和 async 功能很轻松的执行协程。本文的相关代码可以在 github 上找到。
下面关于Python的文章您也可能喜欢,不妨看看:
Ubuntu 14.04 下安装使用Python rq模块
CentOS上源码安装Python3.4
《Python核心编程 第二版》.(Wesley J. Chun ).[高清PDF中文版]