Skip to content
advanced Phase 6 · Python Concurrency

AsyncIO & Async/Await

Write asynchronous code with asyncio, async/await, and event loops.

1h 30m
0 problems
Topic Progress 0%

Async Basics

What is Async I/O?

Asyncio allows concurrent execution using a single thread with cooperative multitasking. Great for I/O-bound tasks with many concurrent connections.

import asyncio

# Define coroutine
def greet(name):
    return f'Hello, {name}!'

# Async function (coroutine)
async def greet_async(name):
    await asyncio.sleep(1)  # Non-blocking sleep
    return f'Hello, {name}!'

# Run coroutine
result = greet_async('Alice')  # Creates coroutine object
result = asyncio.run(greet_async('Alice'))  # Actually runs it

async/await

import asyncio
import time

# Synchronous
def sync_download(url):
    time.sleep(2)  # Blocks for 2 seconds
    return f'Data from {url}'

# Asynchronous
async def async_download(url):
    await asyncio.sleep(2)  # Yields control for 2 seconds
    return f'Data from {url}'

# Running async functions
async def main():
    # Sequential (not what we want)
    result1 = await async_download('url1')  # 2 seconds
    result2 = await async_download('url2')  # 2 seconds
    # Total: 4 seconds
    
    # Concurrent (what we want)
    result1, result2 = await asyncio.gather(
        async_download('url1'),
        async_download('url2')
    )
    # Total: 2 seconds (parallel)

asyncio.run(main())

When to Use Asyncio

# ✅ Use asyncio for:
# - Network I/O (HTTP requests, websockets)
# - Database queries
# - File I/O (with aiofiles)
# - Many concurrent connections

# ❌ Don't use asyncio for:
# - CPU-bound tasks (use multiprocessing)
# - Simple scripts (use threading)
# - Blocking I/O (won't help)

Asyncio Features

Tasks and Gathering

import asyncio

async def fetch_data(url, delay):
    print(f'Fetching {url}...')
    await asyncio.sleep(delay)
    print(f'Done {url}')
    return f'Data from {url}'

async def main():
    # Create tasks
    task1 = asyncio.create_task(fetch_data('url1', 2))
    task2 = asyncio.create_task(fetch_data('url2', 1))
    
    # Gather results
    results = await asyncio.gather(task1, task2)
    print(results)  # ['Data from url1', 'Data from url2']

asyncio.run(main())

Timeouts

import asyncio

async def slow_operation():
    await asyncio.sleep(10)
    return 'Done'

async def main():
    try:
        result = await asyncio.wait_for(slow_operation(), timeout=2)
    except asyncio.TimeoutError:
        print('Operation timed out')

asyncio.run(main())

Async Iterators

class AsyncCounter:
    def __init__(self, stop):
        self.stop = stop
        self.current = 0
    
    def __aiter__(self):
        return self
    
    async def __anext__(self):
        if self.current >= self.stop:
            raise StopAsyncIteration
        self.current += 1
        await asyncio.sleep(0.1)
        return self.current

async def main():
    async for num in AsyncCounter(5):
        print(num)  # 1, 2, 3, 4, 5

asyncio.run(main())

Async Context Managers

import asyncio

class AsyncDatabase:
    async def __aenter__(self):
        print('Connecting...')
        await asyncio.sleep(1)
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print('Disconnecting...')
        await asyncio.sleep(1)
        return False
    
    async def query(self, sql):
        await asyncio.sleep(0.5)
        return f'Result of {sql}'

async def main():
    async with AsyncDatabase() as db:
        result = await db.query('SELECT * FROM users')
        print(result)

asyncio.run(main())

Exception Handling

import asyncio

async def risky_operation():
    await asyncio.sleep(1)
    raise ValueError('Something went wrong')

async def main():
    tasks = [
        asyncio.create_task(risky_operation()),
        asyncio.create_task(asyncio.sleep(2))
    ]
    
    # gather with return_exceptions
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    for result in results:
        if isinstance(result, Exception):
            print(f'Error: {result}')
        else:
            print(f'Result: {result}')

asyncio.run(main())

Asyncio vs Threading

# Asyncio:
# - Single thread
# - Cooperative multitasking
# - More efficient for many I/O operations
# - No race conditions (no shared state)
# - Requires async/await syntax

# Threading:
# - Multiple threads
# - Preemptive multitasking
# - Better for blocking I/O
# - Can have race conditions
# - Simpler syntax (no async/await)

# Rule of thumb:
# - Many network connections -> asyncio
# - Few blocking operations -> threading
# - CPU-bound -> multiprocessing