Video summary

import asyncio: Learn Python's AsyncIO #2 - The Event Loop

Main summary

Key takeaways

Educational

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:
      1. Calls callbacks in some order (one-by-one).
      2. Uses an event demultiplexer (via a selector or proactor) to wait for I/O readiness/completions.
      3. Schedules resulting callbacks for future iterations.
      4. Checks which timers/call-later callbacks are ready.
  • 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 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

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).
  • 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.
  • 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.
  • 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 uvloop
        • uvloop.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.
  • 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).
    • 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.

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)
  • 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

Original video