【发布时间】:2018-02-26 12:56:32
【问题描述】:
我有这个 Cython 代码(简化):
class Callback:
async def foo(self):
print('called')
cdef void call_foo(void* callback):
print('call_foo')
asyncio.wait_for(<object>callback.foo())
async def py_call_foo():
call_foo(Callback())
async def example():
loop.run_until_complete(py_call_foo())
但是会发生什么:我得到RuntimeWarning: coroutine Callback.foo was never awaited。而且,事实上,它从来没有被调用过。但是,call_foo 被调用。
知道发生了什么/如何让它真正等待Callback.foo 完成?
加长版
在上面的例子中,一些重要的细节被遗漏了:特别是,从call_foo 获取返回值真的很困难。真正的项目设置是这样的:
具有规则的 Bison 解析器。规则是对特制结构的引用,我们称之为
ParserState。此结构包含对回调的引用,当规则匹配时由解析器调用。在 Cython 代码中,有一个类,我们称之为
Parser,包的用户应该扩展它来制作他们的自定义解析器。此类具有然后需要从ParserState的回调中调用的方法。-
解析应该是这样发生的:
async def parse_file(file, parser): cdef ParserState state = allocate_parser_state( rule_callbacks, parser, file, ) parse_with_bison(state)
回调具有一般形式:
ctypedef void(callback*)(char* text, void* parser)
我不得不承认我不知道asyncio 究竟是如何实现await 的,所以我不知道一般情况下是否可以使用我拥有的设置来做到这一点。不过,我的最终目标是多个 Python 函数能够或多或少地同时迭代解析不同的文件。
【问题讨论】:
-
我认为你应该在
py_call_foo中await call_foo(...)。此外,您应该在call_foo中return asyncio.wait_for(...)。否则,事件循环在Callback.foo运行完成之前退出,并且 asyncio 会报错。 -
@user4815162342 问题是,实际上,
call_foo是用 C 代码调用的,我没有办法从该调用中获取返回值(更具体地说,它是由代码调用的由Bison生成)。 -
暂停不是基于一些解释器技巧的,完全有可能将其编译成高效的机器代码(这是 Cython 和 PyPy 使用异步函数所做的)。正是因为它不使用技巧,所以它不能神奇地将同步代码转换为异步代码。如果代码是非阻塞的并且基于回调,则可以这样做——想想 JavaScript 风格的“承诺”等。您使用的解析器是否支持“推送”接口?
-
使用push接口解析应该明显慢一点并不明显;例如,以效率着称的 expat XML 解析器支持推送接口。无论哪种方式,如果您的解析器希望“控制”解析过程(即在解析完成之前不返回调用者),您将需要使用线程或其仿真(例如 greenlets)来同时运行多个实例。 asyncio 旨在通过提供暂停原语使其看起来像是在命令式编程,从而使使用回调进行编程变得更容易。
-
这是一项令人印象深刻的工作,尽管它的代价是它使解析器从根本上与 asyncio(以及类似形式的协作多任务处理)不兼容。不过,我想知道,与函数完成的实际解析工作相比,one 堆分配(对于所有令牌)的成本是否真的有所不同?
标签: python cython python-asyncio