- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
我有这个 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 函数能够或多或少同时迭代地解析不同的文件。
最佳答案
简单描述:
协程必须是 await
或由事件循环运行。 cdef
函数不能await
,但它可以构造并返回协程。
您的实际问题是将同步代码与异步代码混合在一起。恰当的例子:
async def example():
loop.run_until_complete(py_call_foo())
这类似于将子例程放入线程中,但从不启动它。即使在启动时,这也是一个死锁:同步部分会阻止异步部分运行。
await
edasync def
协程类似于 def ...: yield
生成器:调用它只会实例化它。您必须与它交互才能实际运行它:
def foo():
print('running!')
yield 1
bar = foo() # no output!
print(next(bar)) # prints `running!` followed by `1`
类似地,当您有一个 async def
协程时,您必须 await
它或将其安排在事件循环中。自 asyncio.wait_for
产生一个协程,你永远不会 await
或安排它,它不会运行。这就是 RuntimeWarning
的原因。
请注意,将协程放入 asyncio.wait_for
的目的纯粹是为了添加超时。它生成一个必须被 await
处理的异步包装器。
async def call_foo(callback):
print('call_foo')
await asyncio.wait_for(callback.foo(), timeout=2)
asyncio.get_event_loop().run_until_complete(call_foo(Callback()))
异步编程的关键在于它是协作:只有一个协程执行直到它让出控制权。之后,另一个协程执行直到它放弃控制。这意味着任何阻塞而不让出控制权的协程也会阻塞所有其他协程。
一般来说,如果某些东西在没有 await
上下文的情况下执行工作,它就是阻塞的。值得注意的是,loop.run_until_complete
是阻塞的。您必须从同步函数中调用它:
loop = asyncio.get_event_loop()
# async def function uses await
async def py_call_foo():
await call_foo(Callback())
# non-await function is not async
def example():
loop.run_until_complete(py_call_foo())
example()
协程可以像常规函数一样返回
结果。
async def make_result():
await asyncio.sleep(0)
return 1
如果你从另一个协程await
它,你直接得到返回值:
async def print_result():
result = await make_result()
print(result) # prints 1
asyncio.get_event_loop().run_until_complete(print_result())
要从常规子例程中的协程获取值,请使用 run_until_complete
运行协程:
def print_result():
result = asyncio.get_event_loop().run_until_complete(make_result())
print(result)
print_result()
cdef/cpdef
函数不能是协程Cython 仅通过 yield from
和 await
支持协程,仅适用于 Python 函数。即使对于经典协程,cdef
也是不可能的:
Error compiling Cython file:
------------------------------------------------------------
cdef call_foo(callback):
print('call_foo')
yield from asyncio.wait_for(callback.foo(), timeout=2)
^
------------------------------------------------------------
testbed.pyx:10:4: 'yield from' not supported here
您完全可以调用来自协程的同步 cdef
函数。您完全可以从 cdef
函数安排协程。但是您不能从 cdef
函数内部 await
,也不能 await
cdef
函数。如果您需要这样做,就像您的示例中那样,请使用常规的 def
函数。
但是,您可以在 cdef
函数中构造并返回协程。这允许您在外部协程中等待
结果:
# inner coroutine
async def pingpong(what):
print('pingpong', what)
await asyncio.sleep(0)
return what
# cdef layer to instantiate and return coroutine
cdef make_pingpong():
print('make_pingpong')
return pingpong('nananana')
# outer coroutine
async def play():
for i in range(3):
result = await make_pingpong()
print(i, '=>', result)
asyncio.get_event_loop().run_until_complete(play())
请注意,尽管有 await
,make_pingpong
不是协程。它只是协程的工厂。
关于python - 如何在cdef中等待?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48989065/
我是一名优秀的程序员,十分优秀!