Nim*_*Nim 11 c++ python python-asyncio pybind11
我们在python中有一个代码库,它使用asyncio和协同例程(async方法和awaits),我想做的是从C++类中调用其中一个方法,这个类被拉入python(使用pybind11)
假设有这样的代码:
class Foo:
async def bar(a, b, c):
# some stuff
return c * a
Run Code Online (Sandbox Code Playgroud)
假设代码是从python中调用的,并且有一个io循环处理它,在某些时候,代码会进入bar需要调用此方法的C++领域- 如何await在C++中实现这一结果?
可以在 C++ 中实现 Python 协程,但需要一些工作。您需要做解释器(在静态语言中编译器)通常为您做的事情,并将您的异步函数转换为状态机。考虑一个非常简单的协程:
async def coro():
x = foo()
y = await bar()
baz(x, y)
return 42
Run Code Online (Sandbox Code Playgroud)
Invokingcoro()不会运行它的任何代码,但它会生成一个可等待的对象,该对象可以启动然后恢复多次。(但您通常不会看到这些操作,因为它们是由事件循环透明地执行的。)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
Run Code Online (Sandbox Code Playgroud)
换句话说,await只要等待的对象挂起(产生)。等待的对象表示它是通过提高StopIteration和在其value属性中走私返回值来完成的。如果产量在-一环听起来yield from,你是完全正确的,这就是为什么await经常被描述而言的yield from。但是,在 C++ 中我们没有yield(尚未),因此我们必须将上述内容集成到状态机中。
要async def从头开始实现,我们需要有一个满足以下约束的类型:
__await__返回可迭代对象的方法,它可以是self;__iter__返回一个迭代器,它可以再次是self;__next__方法,它的调用实现了状态机的一个步骤,返回意味着暂停,提升StopIteration意味着完成。上述协程的状态机 in__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")
Run Code Online (Sandbox Code Playgroud)
我们可以使用真正的 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
Run Code Online (Sandbox Code Playgroud)
剩下的部分是coro用 Python/C 或 pybind11编写类。
小智 5
这不是 pybind11,但您可以直接从 C 调用异步函数。您只需使用 add_done_callback 向未来添加回调。我假设 pybind11 允许您调用 python 函数,因此步骤是相同的:
https://github.com/MarkReedZ/mrhttp/blob/master/src/mrhttp/internals/protocol.c
result = protocol_callPageHandler(self, r->func, request))
Run Code Online (Sandbox Code Playgroud)
现在异步函数的结果是未来。就像在 python 中一样,您需要使用生成的未来调用 create_task :
PyObject *task;
if(!(task = PyObject_CallFunctionObjArgs(self->create_task, result, NULL))) return NULL;
Run Code Online (Sandbox Code Playgroud)
然后你需要使用 add_done_callback 添加一个回调:
add_done_callback = PyObject_GetAttrString(task, "add_done_callback")
PyObject_CallFunctionObjArgs(add_done_callback, self->task_done, NULL)
Run Code Online (Sandbox Code Playgroud)
self->task_done 是在 python 中注册的 C 函数,将在任务完成时调用。