可以在C++中实现Python协程,但需要一些工作。您需要做的是将异步函数转换为状态机,这通常是解释器(在静态语言中是编译器)为您完成的工作。考虑一个非常简单的协程:
async def coro():
x = foo()
y = await bar()
baz(x, y)
return 42
调用 coro()
不会运行任何其代码,但它会生成一个可等待对象,该对象可以启动并多次恢复。 (但通常您看不到这些操作,因为它们由事件循环透明地执行。)可等待对象可以以两种不同的方式响应:通过 1)暂停或 2)指示已完成。
在协程内部,await
实现了暂停。如果使用生成器实现协程,则 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听起来像
yield from
,那么你就是正确的,这就是为什么
await
经常被描述为
yield from
的原因。然而,在C++中我们没有
yield
(但
还没有),所以我们必须将上述内容集成到状态机中。
要从头实现
async def
,我们需要有一个满足以下约束条件的类型:
- 构造时不做太多事情 - 通常只存储接收到的参数
- 具有返回可迭代对象的
__await__
方法,可以是
self
;
- 具有返回迭代器的
__iter__
,可以再次是
self
;
- 具有一个
__next__
方法,其调用实现状态机的一步,其中返回意味着挂起,引发
StopIteration
意味着完成。
上述协程在
__next__
中的状态机将包括三个状态:
1. 初始状态,当它调用
foo()
同步函数时
2. 下一个状态是等待
bar()
协程挂起的时间,传播挂起到调用者。一旦
bar()
返回值,我们就可以立即继续调用
baz()
并通过
StopIteration
异常返回值。
3. 最后一个状态只是引发异常,通知调用者协程已完成。
因此,上面展示的
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)
return suspend_val
except StopIteration as stop:
y = stop.value
baz(self._x, y)
self._state = 2
raise StopIteration(42)
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
类。
await
的调用站点(实际上看起来像什么)。您想在C++中实现一个async def
吗? - user4815162342async def
方法,其中在某些点上有await ..
用于其他异步操作。因此现在 - 与其有一个async
Python方法,我有一个C++函数,并且想要在其中实现相同的效果(好吧,类似的东西)。 - Nim