gpt4 book ai didi

python - 如何在cdef中等待?

转载 作者:太空宇宙 更新时间:2023-11-03 10:51:45 32 4
gpt4 key购买 nike

我有这个 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 中获取返回值真的很困难。真正的项目设置是这样的:

  1. 有规则的 Bison 解析器。规则被赋予对特制结构的引用,我们称之为 ParserState。此结构包含对回调的引用,在规则匹配时由解析器调用。

  2. 在 Cython 代码中,有一个类,我们称它为 Parser,包的用户应该扩展它来制作他们的自定义解析器。此类具有需要从 ParserState 的回调中调用的方法。

  3. 解析应该像这样发生:

    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())

这类似于将子例程放入线程中,但从不启动它。即使在启动时,这也是一个死锁:同步部分会阻止异步部分运行。


异步代码必须awaited

async 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 fromawait 支持协程,仅适用于 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())

请注意,尽管有 awaitmake_pingpong 不是协程。它只是协程的工厂。

关于python - 如何在cdef中等待?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48989065/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com