Asyncio & Event loop
Last updated
Was this helpful?
Last updated
Was this helpful?
There are 3 types awaitable objects
Coroutines
Tasks -
Futures -
A coroutine is a specialized function in Python used for asynchronous programming.
It allows you to pause and resume its execution at certain points, making it ideal for handling tasks that involve waiting (e.g., for I/O operations, timers, or other asynchronous events).
Coroutines work in conjunction with an event loop, which schedules and manages their execution.
Pausing Execution: When a coroutine encounters an await
expression, it pauses its execution and hands control back to the event loop.
Resuming Execution: The coroutine is resumed when the awaited task (e.g., asyncio.sleep(1)
) is completed, allowing other tasks to run in the meantime.
You can create separate tasks that run independently, allowing your program to continue without waiting for one task to finish.
Here, task1()
and task2()
run concurrently, and the program doesn't block while waiting for one task to complete.
asyncio.gather
lets you wait for multiple tasks at the same time without blocking others. It collects results from all the coroutines when they're done.
Event loops use cooperative scheduling: an event loop runs one Task at a time. While a Task awaits for the completion of a Future, the event loop runs other Tasks, callbacks, or performs IO operations
When you call asyncio.run(main())
, it creates a new event loop, runs the main()
coroutine inside that loop, and waits for the coroutine to finish. It ensures the main()
coroutine executes to completion.
How the Event Loop Handles Tasks
Concurrency: The event loop enables tasks to run concurrently, meaning it can switch between tasks when one is waiting (e.g., during await asyncio.sleep()
or an I/O operation). However, tasks do not actually run in parallel unless you explicitly use threads or processes.
Single-threaded Nature: The event loop operates in a single thread. It executes one task at a time but quickly switches between them based on when they are ready to progress (non-blocking behavior).
Task Switching:
When a task reaches an await
point (e.g., waiting for a network response or a timer), it yields control back to the event loop.
The event loop then checks its queue of pending tasks and runs the next task that is ready to continue.
This gives the appearance of "simultaneous" execution but is actually task scheduling.
Asynchronous Programming: Both Python's asyncio
and Node.js's event loop enable non-blocking, asynchronous operations. This makes them ideal for I/O-bound tasks like handling multiple network requests.
Single-threaded: Both event loops run on a single thread, processing tasks in an order determined by the scheduler.
Callbacks and Promises: Node.js primarily uses callbacks and promises for asynchronous handling, while Python uses async/await
, which is conceptually similar to promises.
Native Integration:
Node.js has its event loop implemented directly in its runtime, based on libuv, a C library. The event loop is deeply integrated with Node's non-blocking I/O functions.
Python's asyncio
is a library/module implemented on top of Python's runtime, rather than being a part of the core runtime.
Task Scheduling:
In Node.js, the event loop has phases (e.g., timers, I/O callbacks, idle/prepare, poll, check, and close callbacks). It cycles through these phases repeatedly in the order defined by libuv
.
In Python's asyncio
, tasks are managed within an asyncio
event loop using coroutines. Python doesn't have phases like Node.js but schedules tasks and runs them when their I/O operations complete.
Concurrency:
Node.js is inherently single-threaded, but it uses its thread pool (via libuv
) to offload heavy operations like file system tasks or cryptographic operations.
Python's asyncio
can use await
and coroutines for asynchronous tasks, and for true concurrency, Python can leverage threading or multiprocessing in addition to the asyncio
event loop.
API Design:
Node.js APIs heavily use callbacks and promise-based APIs, making it more callback-centric.
Python's asyncio
uses coroutines with async def
and await
, which are often considered more readable.