如何在cdef等待?

2024-09-28 23:04:11 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个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函数能够迭代地解析不同的文件,或多或少都是在同一时间。在


Tags: 代码parser解析器asyncfoo规则defcallback
1条回答
网友
1楼 · 发布于 2024-09-28 23:04:11

TLDR:

协程必须await'或由事件循环运行。cdef函数不能await,但它可以构造并返回一个协程。在

您的实际问题是将同步代码与异步代码混合在一起。典型案例:

async def example():
    loop.run_until_complete(py_call_foo())

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


异步代码必须是awaited

async def协程与def ...: yield生成器类似:调用它只会实例化它。您必须与它交互才能实际运行它:

^{pr2}$

类似地,当您有一个async def协程时,您必须await或者将其安排在事件循环中。由于^{}生成一个协程,而您从未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()

从协同程序返回值

协同程序可以return产生类似于正则函数的结果。在

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只对Python函数通过yield fromawait支持协同路由。即使对于经典的协同程序,也不可能有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函数内部await,也不能await一个cdef函数。如果您需要这样做,如您的示例中所示,请使用常规的def函数。在

但是,您可以在cdef函数中构造并返回一个协程。这允许您await生成一个外部协同程序:

# 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不是一个协程。它只是一个合作的工厂。在

相关问题 更多 >