Video summary
import asyncio: Learn Python's AsyncIO #2 - The Event Loop
Main summary
Key takeaways
Main ideas / concepts covered
-
Purpose of the series
- Introduces Python AsyncIO as a way to write asynchronous, single-threaded, concurrent programs using coroutines.
- Episode 2 focuses on the fundamental construct: the event loop.
- The series is split into 8 parts to build understanding incrementally.
-
What an AsyncIO event loop is (big picture)
- The event loop repeatedly:
- Calls callbacks in some order (one-by-one).
- Uses an event demultiplexer (via a selector or proactor) to wait for I/O readiness/completions.
- Schedules resulting callbacks for future iterations.
- Checks which timers/call-later callbacks are ready.
- The event loop repeatedly:
-
How to run the event loop
- Run forever: keeps looping until stopped.
- Run until complete: runs until a specific future/task completes.
- The demo emphasizes that the loop can be started and stopped multiple times.
-
Scheduling work: callbacks + “trampolines”
- Callbacks can be registered to run later using loop APIs (the demo schedules timed calls).
- Trampolines are introduced as a key concept:
- A trampoline is a callback that re-registers itself onto the event loop after doing a small unit of work.
- This makes it possible to have repeated behavior without manually scheduling repeatedly.
- Multiple trampolines can coexist in the same loop and interleave, maintaining ordering.
-
Cooperative multitasking and why callbacks must be short
- AsyncIO’s event loop runs only one callback at a time.
- If a callback performs long/blocking computation (the “hog” example), it clogs the event loop and delays everything else (other scheduled callbacks, I/O handling).
- Lesson:
- Avoid long operations in callbacks.
- Keep callbacks small and fast, yielding control frequently.
-
How the loop handles many I/O operations concurrently
- The loop uses I/O multiplexing via:
- Reactor pattern (e.g.,
select-style readiness notification): wait until file descriptors are ready for read/write, then user code performs I/O. - Proactor pattern (e.g., Windows IOCP): the OS performs async I/O and notifies when operations complete; user code is called upon completion.
- Reactor pattern (e.g.,
- The loop uses I/O multiplexing via:
-
Reactor vs proactor naming
- The speaker maps:
- Reactor → framework reacts to I/O readiness events.
- Proactor → OS internally handles async I/O completion; framework/user code receives completion notifications.
- Mention:
- Twisted calls its event loop the “reactor”
- Microsoft uses IOCP for proactors
- The speaker maps:
Methodology / instructions presented (detailed)
-
Event loop control (conceptual steps)
- Start an event loop using:
run_forever()(loop continues until explicitly stopped)- or
run_until_complete(…)(loop stops when the provided future completes)
- Stop the loop when desired (e.g., via a scheduled callback).
- Start an event loop using:
-
Scheduling callbacks
- Register callbacks using the event loop’s scheduling functions:
- immediate scheduling (e.g., “call soon”)
- delayed/timed scheduling (e.g., “call later”)
- Use multiple callbacks to demonstrate that the loop can manage many scheduled actions at once.
- Register callbacks using the event loop’s scheduling functions:
-
Use trampolines for repeated self-scheduling
- Implement a callback that:
- does a small unit of work
- then schedules itself again on the event loop
- Run a trampoline once, then let it repeatedly schedule itself to create interleaving work among multiple trampolines.
- Implement a callback that:
-
Avoid event-loop blocking
- Do not run long-running CPU-bound work inside callbacks.
- Keep callback runtime short so the loop can continue processing timers and I/O readiness.
- The “hog” example illustrates what happens when a callback blocks for seconds: the loop pauses until it returns.
-
Choosing / configuring the selector vs proactor implementations
- The event loop backend depends on OS and available implementations.
- Python may choose the most performant selector by default, but you can override it:
- Configure the event loop to use a specific selector implementation (the video references AsyncIO documentation for setting it).
- On worker/secondary threads:
- Python does not create an event loop automatically.
- If you want one in a secondary thread, you must create/set it manually to avoid subtle bugs (multiple loops, misconfiguration, events going unnoticed).
-
UVLoop recommendation (important practical instruction)
- Recommended implementation:
uvloop - Steps:
- Install:
pip install uvloop(version referenced: ~0.14 at time of recording) - Enable before creating/getting the event loop:
import uvloopuvloop.install()
- Install:
- Critical note:
- Call
uvloop.install()before an event loop is created, otherwise you may need to replace/exchange the event loop to use uvloop.
- Call
- Recommended implementation:
-
Event loop debugging configuration
- Enable event loop debug mode:
- Set event loop debug to
True(the speaker demonstrates toggling it on an already-created loop).
- Set event loop debug to
- Effect:
- AsyncIO logs warnings when callbacks take too long (e.g., the “hog” callback),
- including details like:
- which callback/timer was running
- where it was defined/scheduled from
- how long it took.
- Enable event loop debug mode:
Platform/implementation details mentioned
-
Multiple event loop implementations exist
- The video explains structure:
- AbstractEventLoop (interface)
- BaseEventLoop (includes the main loop mechanics)
- SelectorEventLoop variants (reactor-style)
- ProactorEventLoop variants (proactor-style)
- The video explains structure:
-
Windows
- Uses Proactor (IOCP-based) and has a Selector loop as well.
- Proactor scales better (kernel-level async I/O completion).
- Selector loop limitations mentioned:
- up to around 512 sockets
- only supports sockets (not pipes/subprocesses in the same way)
-
Unix-like systems
- “Selector event loop” is the main path.
- Python picks the most efficient selector:
- kqueue on BSD/macOS
- epoll on Linux
- /dev/poll (mentioned as Solaris) or poll (mentioned as another option)
-
Why selector differences matter
- The event loop is described as the tightest loop in an AsyncIO program.
- Performance characteristics of selectors can matter for scalability.
Sources / speakers featured (end)
- Speaker: Lucas (intro identifies him as “Lucas from HDB”)
- Frameworks / technologies referenced (not speaking sources):
- Python AsyncIO
- uvloop
- libuv (via uvloop)
- Twisted (reactor naming)
- Microsoft IOCP
- selectors module
- kqueue, epoll, poll/select
- PDB
- logging module