Async Python lets you do more with less waiting. Here's how it works.

The Problem

import time
 
def fetch_data(url):
    time.sleep(1)  # Simulate network request
    return f"Data from {url}"
 
# Sequential - takes 3 seconds
results = [fetch_data(url) for url in urls]

While waiting for one request, you could be making others.

The Solution

import asyncio
 
async def fetch_data(url):
    await asyncio.sleep(1)  # Non-blocking wait
    return f"Data from {url}"
 
async def main():
    # Concurrent - takes ~1 second total
    results = await asyncio.gather(
        fetch_data("url1"),
        fetch_data("url2"),
        fetch_data("url3"),
    )
 
asyncio.run(main())

async and await

# async defines a coroutine
async def my_function():
    # await pauses until result is ready
    result = await some_async_operation()
    return result

await can only be used inside async functions.

Running Async Code

# Main entry point
asyncio.run(main())
 
# Or get the event loop
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Concurrent Execution

gather - Run Multiple Tasks

async def main():
    results = await asyncio.gather(
        fetch("url1"),
        fetch("url2"),
        fetch("url3"),
    )
    # results = [result1, result2, result3]

create_task - Fire and Manage

async def main():
    task1 = asyncio.create_task(fetch("url1"))
    task2 = asyncio.create_task(fetch("url2"))
    
    # Do other work while tasks run
    
    result1 = await task1
    result2 = await task2

as_completed - Process as Ready

async def main():
    tasks = [fetch(url) for url in urls]
    
    for coro in asyncio.as_completed(tasks):
        result = await coro
        print(f"Got: {result}")  # Process immediately

Timeouts

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

Real HTTP Requests

Use httpx or aiohttp:

import httpx
 
async def fetch_all(urls):
    async with httpx.AsyncClient() as client:
        tasks = [client.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
        return [r.json() for r in responses]

Async Context Managers

class AsyncResource:
    async def __aenter__(self):
        await self.connect()
        return self
    
    async def __aexit__(self, *args):
        await self.disconnect()
 
async def main():
    async with AsyncResource() as resource:
        await resource.do_something()

Async Iterators

async def fetch_pages():
    page = 1
    while True:
        data = await fetch_page(page)
        if not data:
            break
        yield data
        page += 1
 
async def main():
    async for page in fetch_pages():
        process(page)

Common Patterns

Semaphore - Limit Concurrency

async def fetch_with_limit(urls, max_concurrent=10):
    semaphore = asyncio.Semaphore(max_concurrent)
    
    async def fetch_one(url):
        async with semaphore:
            return await fetch(url)
    
    return await asyncio.gather(*[fetch_one(url) for url in urls])

Queue - Producer/Consumer

async def producer(queue):
    for item in items:
        await queue.put(item)
 
async def consumer(queue):
    while True:
        item = await queue.get()
        await process(item)
        queue.task_done()
 
async def main():
    queue = asyncio.Queue()
    
    producers = [asyncio.create_task(producer(queue))]
    consumers = [asyncio.create_task(consumer(queue)) for _ in range(3)]
    
    await asyncio.gather(*producers)
    await queue.join()  # Wait for all items processed
    
    for c in consumers:
        c.cancel()

When to Use Async

Good for:

  • I/O bound operations (network, disk)
  • Many concurrent connections
  • Web servers handling many requests
  • Web scraping

Not helpful for:

  • CPU-bound work (use multiprocessing)
  • Simple scripts
  • When you have few concurrent operations

Common Mistakes

Blocking in Async Code

# Bad - blocks the event loop
async def bad():
    time.sleep(1)  # Blocks everything!
    
# Good
async def good():
    await asyncio.sleep(1)  # Non-blocking

Forgetting await

# Wrong - returns coroutine object, not result
result = fetch_data()
 
# Right
result = await fetch_data()

Mixing Sync and Async

# Can't await in regular function
def regular_function():
    await async_thing()  # SyntaxError!
 
# Must be async
async def async_function():
    await async_thing()  # OK

Running Sync Code in Async

import asyncio
 
def blocking_operation():
    time.sleep(1)
    return "done"
 
async def main():
    # Run in thread pool
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(None, blocking_operation)

My Guidelines

  1. Use async for I/O - network, database, files
  2. Limit concurrency - don't overwhelm servers
  3. Handle errors - use try/except in tasks
  4. Use httpx/aiohttp - not requests
  5. Keep it simple - don't async everything

Async adds complexity. Use it when you need concurrency.

React to this post: