I have this Cython code (simplified):
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())
What happens though: I get RuntimeWarning: coroutine Callback.foo was never awaited
. And, in fact, it is never called. However, call_foo
is called.
Any idea what's going on / how to get it to actually wait for Callback.foo
to complete?
Extended version
In the example above some important details are missing: In particular, it is really difficult to get hold of return value from call_foo
. The real project setup has this:
Bison parser that has rules. Rules are given a reference to specially crafted struct, let's call it
ParserState
. This struct contains references to callbacks, which are called by parser when rules match.In Cython code, there's a class, let's call it
Parser
, that users of the package are supposed to extend to make their custom parsers. This class has methods which then need to be called from callbacks ofParserState
.Parsing is supposed to happen like this:
async def parse_file(file, parser): cdef ParserState state = allocate_parser_state( rule_callbacks, parser, file, ) parse_with_bison(state)
The callbacks are of a general shape:
ctypedef void(callback*)(char* text, void* parser)
I have to admit I don't know how exactly asyncio
implements await
, and so I don't know if it is in general possible to do this with the setup that I have. My ultimate goal though is that multiple Python functions be able to iteratively parse different files, all at the same time more or less.
Your problem is mixing synchronous with asynchronous code. Case in point:
This is similar to putting a subroutine in a Thread, but never starting it. Even when started, this is a deadlock: the synchronous part would prevent the asynchronous part from running.
Asynchronous code must be
await
edAn
async def
coroutine is similar to adef ...: yield
generator: calling it only instantiates it. You must interact with it to actually run it:Similarly, when you have an
async def
coroutine, you must eitherawait
it or schedule it in an event loop. Sinceasyncio.wait_for
produces a coroutine, and you neverawait
or schedule it, it is not run. This is the cause for your RuntimeWarning.Note that the purpose of putting a coroutine into
asyncio.wait_for
is purely to add a timeout. It is not meant toawait
or run the coroutine by itself.Asynchronous functions need asynchronous instructions
The key for asynchronous programming is that it is cooperative: Only one coroutine executes until it yields control. Afterwards, another coroutine executes until it yields control. This means that any coroutine blocking without yielding control blocks all other coroutines as well.
In general, if something performs work without an
await
context, it is blocking. Notably,loop.run_until_complete
is blocking. You have to call it from a synchronous function:Return values from coroutines
A coroutine can
return
results like a regular function.If you
await
it from another coroutine, you directly get the return value:To get the value from a regular subroutine, use
run_until_complete
to get the return value:A
cdef/cpdef
function cannot be a coroutineCython supports coroutines via
yield from
andawait
only for Python functions. Even for a classical coroutine, acdef
is not possible:You are perfectly fine calling a synchronous
cdef
function from a coroutine. You are perfectly fine scheduling a coroutine from acdef
function. But you cannotawait
from inside acdef
function, norawait
acdef
function. If you need to do that, as in your example, use a regulardef
function.You can however construct and return a coroutine in a
cdef
function. This allows you toawait
the result in an outer coroutine:Note that despite the
await
,make_pingpong
is not a coroutine. It is merely a factory for coroutines.