可以在 C++ 中实现 Python 协程,但需要一些工作。您需要执行解释器(在静态语言中为编译器)通常为您执行的操作,并将您的异步函数转换为状态机。考虑一个非常简单的协程:
async def coro():
x = foo()
y = await bar()
baz(x, y)
return 42
调用coro() 不会运行它的任何代码,但它会生成一个awaitable 对象,该对象可以启动然后再恢复多次。 (但您通常不会看到这些操作,因为它们是由事件循环透明地执行的。)可等待对象可以通过两种不同的方式响应:1)挂起,或 2)表明它已完成。
在协程await 内实现暂停。如果使用生成器实现协程,y = await bar() 将脱糖:
# pseudo-code for y = await bar()
_bar_iter = bar().__await__()
while True:
try:
_suspend_val = next(_bar_iter)
except StopIteration as _stop:
y = _stop.value
break
yield _suspend_val
换句话说,await 暂停(屈服),只要等待的对象这样做。等待的对象通过引发StopIteration 并通过在其value 属性中走私返回值来表明它已完成。如果 yield-in-a-loop 听起来像 yield from,那么您是完全正确的,这就是为什么 await 经常被描述为yield from 的术语。但是,在C++中我们没有yield(yet),所以我们必须将上面的内容集成到状态机中。
要从头实现async def,我们需要一个满足以下约束的类型:
- 在构造时并没有做太多 - 通常它只会存储它收到的参数
- 有一个
__await__ 方法返回一个可迭代对象,可以是self;
- 有一个
__iter__,它返回一个迭代器,它又可以是self;
- 有一个
__next__方法,其调用实现了状态机的一步,return表示暂停,raiseStopIteration表示结束。
__next__ 中上述协程的状态机将包含三个状态:
- 第一个,当它调用
foo()同步函数时
- 当它一直等待
bar() 协程时的下一个状态,只要它挂起(传播挂起)给调用者。一旦bar() 返回一个值,我们可以立即继续调用baz() 并通过StopIteration 异常返回该值。
- 最终状态,它简单地引发异常,通知调用者协程已用完。
所以上面显示的async def coro() 定义可以被认为是以下的语法糖:
class coro:
def __init__(self):
self._state = 0
def __iter__(self):
return self
def __await__(self):
return self
def __next__(self):
if self._state == 0:
self._x = foo()
self._bar_iter = bar().__await__()
self._state = 1
if self._state == 1:
try:
suspend_val = next(self._bar_iter)
# propagate the suspended value to the caller
# don't change _state, we will return here for
# as long as bar() keeps suspending
return suspend_val
except StopIteration as stop:
# we got our value
y = stop.value
# since we got the value, immediately proceed to
# invoking `baz`
baz(self._x, y)
self._state = 2
# tell the caller that we're done and inform
# it of the return value
raise StopIteration(42)
# the final state only serves to disable accidental
# resumption of a finished coroutine
raise RuntimeError("cannot reuse already awaited coroutine")
我们可以使用真正的 asyncio 测试我们的“协程”是否正常工作:
>>> class coro:
... (definition from above)
...
>>> def foo():
... print('foo')
... return 20
...
>>> async def bar():
... print('bar')
... return 10
...
>>> def baz(x, y):
... print(x, y)
...
>>> asyncio.run(coro())
foo
bar
20 10
42
剩下的部分就是用Python/C或者pybind11写coro类。