Event loop is the heart of asyncio. It doesn’t run code in parallel across multiple threads. Instead, it switches between tasks the moment they wait for I/O — and that’s how concurrency is achieved in a single thread.
I/O bound vs CPU bound
import time, asyncio
# CPU bound — async won't help
def sum_squares(n):
return sum(i * i for i in range(n)) # pure computation
# I/O bound — async will speed it up
async def fetch(url):
await asyncio.sleep(1) # simulating network wait
Rule: if code waits (network, disk, database) — async gives a speedup. If it computes — it doesn’t.
How the Event Loop Works
import asyncio
async def task(name, delay):
print(f"{name}: started")
await asyncio.sleep(delay) # <-- event loop switches here
print(f"{name}: done")
async def main():
# Without gather — sequential, 3 seconds
await task("A", 1)
await task("B", 2)
asyncio.run(main())
At every await the event loop checks: are there other tasks ready to run? If yes — it runs them. That’s why asyncio.gather() is faster than sequential await calls.
Future and Task
Future — a low-level object: a “promise” that a result will come later.
Task — a wrapper around a coroutine that the event loop runs concurrently.
async def main():
# asyncio.create_task() wraps coroutine in a Task
# and schedules it for immediate execution
task1 = asyncio.create_task(task("A", 1))
task2 = asyncio.create_task(task("B", 2))
# Both tasks are already running. await here just waits for completion
await task1
await task2
Debug Mode
import asyncio, logging
logging.basicConfig(level=logging.DEBUG)
# Warns about slow coroutines (> 100ms)
asyncio.run(main(), debug=True)
In debug mode asyncio warns when a coroutine holds the CPU too long — this helps find accidentally blocking code.
💬 Comments (0)
No comments yet
Be the first to share your opinion about this article!