Asyncio

AsyncIO is the backbone of asynchronous programming in Python. To build high-performance systems — APIs, websocket servers, scrapers, automation pipelines, or distributed workers — you must fully understand how the event loop works, how Tasks provide concurrency, and how Futures act as low-level building blocks.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

🔥 1. What Exactly Is the Event Loop?

The event loop is a scheduler that repeatedly:

It's the "orchestra conductor" of asynchronous execution.

⚙️ 2. Creating Coroutines (The Basics)

A coroutine is a function that can be paused:

Coroutines don't run until awaited or turned into a Task.

🧠 3. Tasks — The Core of Concurrency

A Task wraps a coroutine and schedules it on the event loop so it runs concurrently.

This is how we achieve concurrency in a single thread.

⚡ 4. Running Multiple Tasks Concurrently

🌀 5. Futures — Low-Level Awaitables

A Future represents a placeholder for a value that isn't available yet.

You rarely create Futures manually, but Tasks and event-loop internals rely on them.

Tasks are built on Futures — every Task is a subclass of Future.

⏳ 6. Understanding How Tasks Progress

A task runs until it hits an await that yields control:

🧩 7. Task Cancellation

🧱 8. Task Groups (Python 3.11+)

⚡ 9. Wait vs Gather — When To Use Which?

⏱️ 10. Using Timeouts Correctly

Timeouts are essential for robust production systems.

🔄 11. Callbacks & Event Loop Scheduling

This gives event-loop-level control that frameworks use internally.

🛰️ 12. Real-World Example — Concurrent API Fetching

This is how modern backend services fetch data from multiple microservices at once.

📡 13. Real-World Example — WebScraping With Concurrency

This pattern lets you scrape hundreds of pages per second.

🔥 14. Production Architecture Using Tasks

🎉 Conclusion

You've mastered three critical components of AsyncIO:

Concurrent execution wrappers built on Futures

Low-level placeholders controlling async flow

Together, these form the foundation of every major async Python framework (FastAPI, Starlette, aiohttp).

📋 Quick Reference — AsyncIO

Syntax

What it does

asyncio.get_event_loop()

Get the current event loop

asyncio.create_task(coro)

Schedule coroutine as background task

asyncio.wait_for(coro, timeout)

Add timeout to a coroutine

asyncio.Queue()

Thread-safe async queue

async for / async with

Async iteration and context managers

You now know how the asyncio event loop works internally, how to manage Tasks, and how to build async pipelines.

Up next: Concurrency — compare threads vs processes and choose the right model.

Practice quiz

What does asyncio.run(main()) do?

  • Defines a coroutine without running it
  • Schedules main() as a background task
  • Creates an event loop, runs the coroutine, then closes the loop
  • Runs main() in a separate process

Answer: Creates an event loop, runs the coroutine, then closes the loop. asyncio.run() creates an event loop, runs the top-level coroutine, and cleans up the loop.

When does a plain coroutine actually start executing?

  • Only when awaited or turned into a Task
  • As soon as it is defined
  • When the module is imported
  • Immediately on the next line

Answer: Only when awaited or turned into a Task. Coroutines don't run until they are awaited or scheduled as a Task.

What is the relationship between Tasks and Futures in asyncio?

  • They are unrelated
  • Every Future is a subclass of Task
  • Futures replaced Tasks in Python 3.11
  • Every Task is a subclass of Future

Answer: Every Task is a subclass of Future. Tasks are built on Futures — every Task is a subclass of Future.

What does asyncio.create_task(coro()) do?

  • Awaits the coroutine and blocks
  • Wraps the coroutine in a Task and schedules it to run concurrently
  • Creates a new event loop
  • Runs the coroutine in a thread

Answer: Wraps the coroutine in a Task and schedules it to run concurrently. create_task wraps a coroutine in a Task and schedules it on the running event loop.

Running two coroutines that each await asyncio.sleep(1) with asyncio.gather takes about how long?

  • 1 second
  • 2 seconds
  • 0 seconds
  • It depends on CPU cores

Answer: 1 second. gather overlaps the awaits, so total runtime is ~1 second, not 2.

What does a Future represent?

  • A finished computation
  • A new OS thread
  • A placeholder for a value that isn't available yet
  • A synchronous callback

Answer: A placeholder for a value that isn't available yet. A Future is a placeholder for a result that will arrive later.

Compared with gather, what extra control does asyncio.wait give you?

  • It runs tasks in parallel processes
  • You can choose return_when=FIRST_COMPLETED or FIRST_EXCEPTION
  • It automatically retries failed tasks
  • It guarantees ordered results

Answer: You can choose return_when=FIRST_COMPLETED or FIRST_EXCEPTION. wait returns (done, pending) and lets you specify FIRST_COMPLETED, ALL_COMPLETED, or FIRST_EXCEPTION.

How do you add a timeout to an awaitable?

  • asyncio.timeout_after(coro, 3)
  • coro.timeout(3)
  • asyncio.sleep(3, coro)
  • asyncio.wait_for(coro, timeout=3)

Answer: asyncio.wait_for(coro, timeout=3). asyncio.wait_for(coro, timeout=3) raises asyncio.TimeoutError if the coroutine takes too long.

What happens to a task when you call task.cancel()?

  • It is paused and can resume later
  • asyncio.CancelledError is raised inside the task
  • It returns None immediately
  • The whole event loop stops

Answer: asyncio.CancelledError is raised inside the task. cancel() schedules a CancelledError to be raised inside the task, which it can catch to clean up.

What is a key benefit of asyncio.TaskGroup (Python 3.11+) over gather?

  • It runs on multiple cores
  • It is faster for CPU-bound work
  • Automatic error propagation and structured concurrency
  • It avoids the event loop entirely

Answer: Automatic error propagation and structured concurrency. TaskGroup provides structured concurrency with automatic error propagation and cleaner code than gather.

Continue this course