Asyncio & Event loop
Awaitable
There are 3 types awaitable objects
Coroutines
Tasks - asyncio.Task
Futures - asyncio.Future
Coroutine
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.
async def my_coroutine():
print("Start")
await asyncio.sleep(1) # Pause execution here
print("Resume after 1 second")
How Coroutines Work
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.
Task
You can create separate tasks that run independently, allowing your program to continue without waiting for one task to finish.
Here,
task1()
andtask2()
run concurrently, and the program doesn't block while waiting for one task to complete.
import asyncio
async def task1():
print("Task 1 started")
await asyncio.sleep(2) # Pause Task 1
print("Task 1 completed")
async def task2():
print("Task 2 started")
await asyncio.sleep(1) # Pause Task 2
print("Task 2 completed")
async def main():
# Create both tasks
t1 = asyncio.create_task(task1())
t2 = asyncio.create_task(task2())
# Wait for both to complete
await t1
await t2
asyncio.run(main())
/**
Task 1 started
Task 2 started
Task 2 completed
Task 1 completed
**/
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.
import asyncio
async def task1():
await asyncio.sleep(5)
print("Task 1 done")
async def task2():
print("Task 2 running")
await asyncio.sleep(2)
print("Task 2 done")
async def main():
results = await asyncio.gather(task1(), task2())
print("All tasks completed:", results)
asyncio.run(main())
Event Loop
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 themain()
coroutine inside that loop, and waits for the coroutine to finish. It ensures themain()
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.
Vs Nodejs
Similarities
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.
Differences
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 anasyncio
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 useawait
and coroutines for asynchronous tasks, and for true concurrency, Python can leverage threading or multiprocessing in addition to theasyncio
event loop.
API Design:
Node.js APIs heavily use callbacks and promise-based APIs, making it more callback-centric.
Python's
asyncio
uses coroutines withasync def
andawait
, which are often considered more readable.
References
Last updated
Was this helpful?