11.3. The event loop¶
The event loop is the engine asyncio runs underneath. It
keeps a list of every task in the program, asks each one to
run until that task’s next await, and moves on to the
next ready task. When there are no ready tasks it waits –
the actual wait is what makes the CPU available for
firmware to run other things and for power-saving sleeps to
kick in – until something a task was awaiting becomes
available, then resumes that task. Repeat forever.
Most applications never interact with the loop directly. The
loop is a consequence of calling asyncio.run(); the
application writes coroutines, schedules them as tasks, and
the loop does the rest.
11.3.1. What asyncio.run() actually does¶
A single call:
asyncio.run(main())
is shorthand for a longer sequence the loop manages on the application’s behalf:
Create the event loop if it does not already exist.
Wrap the supplied coroutine in a task and schedule it as the loop’s top-level entry point.
Run the loop – step through ready tasks, wait when none are ready, resume tasks when their awaits complete – until the top-level task returns or raises.
Cancel any tasks the application created that are still running.
Return whatever the top-level coroutine returned (or re-raise whatever it raised).
11.3.2. Single loop per program¶
MicroPython’s asyncio has one event loop, full stop. There
is no creating a fresh loop, and there is no nesting one
loop inside another. Calling asyncio.run() from inside
a coroutine that is already running on the loop is an error;
the loop is already there, and the coroutine just needs to
await whatever it wanted to start.
In practice the rule is the same as the closing line of the
previous page: there is exactly one asyncio.run() call
per program, at the top, with a single async def main()
behind it. Everything else lives inside main.
11.3.3. Direct loop access¶
For the rare cases an application needs to touch the loop
itself – mostly diagnostics and exception handlers –
asyncio.get_event_loop() returns the
Loop object. From there the application
can install a custom exception handler, inspect what the
loop is doing, or (very occasionally) call
create_task() directly instead of
asyncio.create_task() (they are the same operation).
The full set of methods Loop exposes –
run_forever(),
stop(),
set_exception_handler(), and the rest –
is covered in the loop control page later in this section. Until
then, asyncio.run(main()) is all an application needs.