13.4. AsyncIO Coroutine

  • Coroutine function and coroutine object are two different things

  • Coroutine function is the definition (async def)

  • Coroutine function will create coroutine when called

  • This is similar to generator object and generator function

  • Coroutine functions are not awaitables

  • Coroutine objects are awaitables

  • Coroutines declared with the async/await syntax is the preferred way of writing asyncio applications. [2]

  • https://peps.python.org/pep-0492/

13.4.1. Syntax

>>> async def hello():
...     return 'hello'
>>>
>>>
>>> type(hello)
<class 'function'>
>>>
>>> type(hello())
<class 'coroutine'>

13.4.2. SetUp

>>> import asyncio

13.4.3. Coroutine Function

  • Coroutine function is the definition (async def)

  • Also known as async functions

  • Defined by putting async in front of the def

  • Only a coroutine function (async def) can use await

  • Non-coroutine functions (def) cannot use await

  • Coroutine functions are not awaitables

Calling a coroutine function does not execute it, but rather returns a coroutine object. This is analogous to generator functions - calling them doesn't execute the function, it returns a generator object, which we then use later.

>>> async def hello():
...     return 'hello'

13.4.4. Coroutine Object

  • Coroutine function will create coroutine when called

  • Coroutine objects are awaitables

  • To execute coroutine object you can await it

  • To execute coroutine object you can asyncio.run()

  • To schedule coroutine object: ensure_future() or create_task()

To execute a coroutine object, either:

  • use it in an expression with await in front of it, or

  • use asyncio.run(), or

  • schedule it with ensure_future() or create_task().

>>> async def hello():
...     return 'hello'
>>>
>>>
>>> asyncio.run(hello())
'hello'

13.4.5. Run Sequentially

  • All lines inside of coroutine function will be executed sequentially

>>> async def hello():
...     await asyncio.sleep(0.1)
...     return 'hello'
>>>
>>>
>>> asyncio.run(hello())
'hello'

All lines inside of coroutine function will be executed sequentially. When await happen, other coroutine will start running. When other coroutine finishes, it returns to our function. Then next line is executed (which could also be an await statement:

>>> async def hello():
...     await asyncio.sleep(0.1)
...     await asyncio.sleep(0.1)
...     await asyncio.sleep(0.1)
...     return 'hello'
>>>
>>>
>>> asyncio.run(hello())
'hello'

13.4.6. Run Concurrently

  • To run coroutine objects use asyncio.gather()

  • This won't execute in parallel (won't use multiple threads)

  • It will run concurrently (in a single thread)

>>> async def hello():
...     await asyncio.sleep(0.1)
>>>
>>> async def main():
...     await asyncio.gather(
...         hello(),
...         hello(),
...         hello(),
...     )
>>>
>>> asyncio.run(main())
../../_images/asyncio-coroutine-concurrency.gif

Figure 13.15. Only one hammer is hitting the pole in the same time, however the work continues to be done concurrently. This is faster than one worker with one hammer. Source [1]

13.4.7. Error: Created but not awaited

  • Created but not awaited objects will raise an exception

  • This prevents from creating coroutines and forgetting to "await" on it

13.4.8. Error: Running Coroutine Functions

  • Only coroutine objects can be run

  • It is not possible to run coroutine function

>>> def hello():
...     return 'hello'
>>>
>>>
>>> asyncio.run(hello)  
Traceback (most recent call last):
ValueError: a coroutine was expected, got <function hello at 0x...>

13.4.9. Error: Multiple Awaiting

  • Coroutine object can only be awaited once

>>> async def hello():
...     return 'hello'
>>>
>>>
>>> coro = hello()
>>>
>>> asyncio.run(coro)
'hello'
>>>
>>> asyncio.run(coro)
Traceback (most recent call last):
RuntimeError: cannot reuse already awaited coroutine

13.4.10. Error: Await Outside Coroutine Function

  • Only a coroutine function (async def) can use await

  • Non-coroutine functions (def) cannot use await

>>> def hello():
...     await asyncio.sleep(0.1)
...     return 'hello'
...
Traceback (most recent call last):
SyntaxError: 'await' outside async function

13.4.11. Getting Results

>>> async def hello():
...     await asyncio.sleep(0.1)
...     return 'hello'
>>>
>>>
>>> async def main():
...     return await hello()
>>>
>>>
>>> asyncio.run(main())
'hello'
>>> async def hello():
...     await asyncio.sleep(0.1)
...     return 'hello'
>>>
>>> async def main():
...     return await asyncio.gather(
...         hello(),
...         hello(),
...         hello(),
...     )
>>>
>>> asyncio.run(main())
['hello', 'hello', 'hello']

13.4.12. Inspect

>>> from inspect import isawaitable
>>>
>>>
>>> async def hello():
...     return 'hello'
>>>
>>>
>>> isawaitable(hello)
False
>>>
>>> isawaitable(hello())
True
>>>
>>>
>>> type(hello)
<class 'function'>
>>>
>>> type(hello())
<class 'coroutine'>

13.4.13. References