我们在python中有一个使用asyncio和协同例程(async
方法和await
s)的代码库,我想做的是从C ++调用这些方法之一被拉入python的类(使用pybind11)
假设有这样的代码:
class Foo:
async def bar(a, b, c):
# some stuff
return c * a
假设代码是从python调用的,并且有一个处理此问题的io循环,在某些时候,代码会放到需要调用此bar
方法的C ++领域-一个{{1 }}在C ++中的结果?
答案 0 :(得分:3)
这不是pybind11,但是您可以直接从C调用异步函数。您只需使用add_done_callback将回调添加到将来。我假设pybind11允许您调用python函数,因此步骤将是相同的:
https://github.com/MarkReedZ/mrhttp/blob/master/src/mrhttp/internals/protocol.c
PyObject *task;
if(!(task = PyObject_CallFunctionObjArgs(self->create_task, result, NULL))) return NULL;
现在,异步功能的结果是未来。就像在python中一样,您需要使用生成的Future来调用create_task:
add_done_callback = PyObject_GetAttrString(task, "add_done_callback")
PyObject_CallFunctionObjArgs(add_done_callback, self->task_done, NULL)
然后您需要使用add_done_callback添加回调:
const std::string setup_exts[] = { "zst", "xz", "bz2", "ini" };
self-> task_done是在python中注册的C函数,任务完成后将被调用。
答案 1 :(得分:3)
可以在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 from
,那么您说得很对,这就是为什么await
经常用yield from
的术语来描述的原因。但是,在C ++中,我们没有yield
(yet),因此我们必须将以上内容集成到状态机中。
要从头开始实现async def
,我们需要具有满足以下约束的类型:
__await__
方法,该方法返回一个可迭代的对象,该方法可以只是self
; __iter__
,它返回一个迭代器,该迭代器也可以是self
; __next__
方法,其调用实现了状态机的一步,其中return表示暂停,而升高StopIteration
表示完成。 __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")
我们可以使用真正的异步测试我们的“协程”:
>>> 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
类。
答案 2 :(得分:0)
对于这样的事情,如果我不想深入研究CPython API,我只是用Python编写我的东西,然后使用pybind
的Python接口来调用它。
一个例子: https://github.com/RobotLocomotion/drake/blob/a7700d3/bindings/pydrake/init.py#L44 https://github.com/RobotLocomotion/drake/blob/a7700d3/bindings/pydrake/pydrake_pybind.h#L359
渲染到此用例,也许您可以这样做:
# cpp_helpers.py
def await_(obj):
return await obj
py::object py_await = py::module::import("cpp_helpers").attr("await_");
auto result = py::cast<MyResult>(py_await(py_obj));
但是,这很可能会比上述解决方案性能差。