Making a C Function Asynchronous¶
Let's make a C function that replicates the following Python code:
async def hello() -> None:
print("Hello, PyAwaitable")
If you've tried to implement an asynchronous C function in the past, this is likely where you got stuck. How do we make a C function async
?
Breaking Down Awaitable Functions¶
In Python, you have to call an async def
function to use it with await
. In our example above, the following would be invalid:
>>> await hello
Of course, you need to do await hello()
instead. hello()
is returning a coroutine, and coroutine objects are usable with the await
keyword. So, hello
as a synchronous function would really be like:
class _hello_coroutine:
def __await__(self) -> collections.abc.Generator:
print("Hello, PyAwaitable")
yield
def hello() -> collections.abc.Coroutine:
return _hello_coroutine()
If there were to be await
expressions inside hello
, the returned coroutine object would handle those by yielding inside of the __await__
dunder method. We can do the same kind of thing in C.
Creating PyAwaitable Objects¶
You can create a new PyAwaitable object with PyAwaitable_New
. This returns a strong reference to a PyAwaitable object, and NULL
with an exception set on failure.
Think of a PyAwaitable object sort of like the _hello_coroutine
example from above, but it's generic instead of being special for hello
. So, like our Python example, we need to return the coroutine to allow it to be used in await
expressions:
static PyObject *
hello(PyObject *self, PyObject *nothing) // METH_NOARGS
{
PyObject *awaitable = PyAwaitable_New();
if (awiatable == NULL) {
return NULL;
}
puts("Hello, PyAwaitable");
return awaitable;
}
There's a difference between native coroutines and implemented coroutines
"Coroutine" is a bit of an ambigious term in Python. There are two types of coroutines: native ones (types.CoroutineType
), and objects that implement the coroutine protocol (collections.abc.Coroutine
). Only the interpreter itself can create native coroutines, so a PyAwaitable object is an object that implements the coroutine protocol.
Yay! We can now use hello
in await
expressions:
>>> from _yourmod import hello
>>> await hello()
Hello, PyAwaitable
Changing the Return Value¶
Note that in all code-paths, we should return the PyAwaitable object, or NULL
with an exception set to indicate a failure. But that means you can't simply return
your own value; how can the await
expression evaluate to something useful?
By default, the "return value" (i.e., what await
will evaluate to) is None
. That can be changed with PyAwaitable_SetResult
, which takes a reference to the object you want to return.
For example, if you wanted to return the Python integer 42
from hello
, you would simply pass that to PyAwaitable_SetResult
:
static PyObject *
hello(PyObject *self, PyObject *nothing) // METH_NOARGS
{
PyObject *awaitable = PyAwaitable_New();
if (awiatable == NULL) {
return NULL;
}
PyObject *my_number = PyLong_FromLong(42);
if (my_number == NULL) {
Py_DECREF(awaitable);
return NULL;
}
if (PyAwaitable_SetResult(awaitable, my_number) < 0) {
Py_DECREF(awaitable);
Py_DECREF(my_number);
return NULL;
}
Py_DECREF(my_number);
puts("Hello, PyAwaitable");
return awaitable;
}
Now, the await
expression evalutes to 42
:
>>> from _yourmod import hello
>>> await hello()
Hello, PyAwaitable
42