How to Propagate Context Variables with asyncio in Python

Use Python's ContextVar with asyncio to carry deadline information across concurrent tasks and propagate context automatically.

Medium Python 3.7+ Aug 9, 2026 Reliability & rate limiting 13 views 0 copies

Python code

30 lines
Python 3.7+
import asyncio
from contextvars import ContextVar
from datetime import datetime

deadline = ContextVar("deadline", default=None)

async def worker(name):
    current = deadline.get()
    if current:
        print(f"{name} sees deadline: {current}")
    else:
        print(f"{name} sees no deadline")
    await asyncio.sleep(0.1)
    print(f"{name} done")

async def main():
    print("Without context (main)")
    await worker("worker-a")
    await worker("worker-b")

    print("\nWith context (main)")
    token = deadline.set(datetime(2025, 1, 31))
    try:
        await worker("worker-a")
        await worker("worker-b")
    finally:
        deadline.reset(token)

if __name__ == "__main__":
    asyncio.run(main())

Output

stdout
Without context (main)
worker-a sees deadline: 2025-01-31 00:00:00
worker-a done
worker-b sees deadline: 2025-01-31 00:00:00
worker-b done

With context (main)
worker-a sees deadline: 2025-01-31 00:00:00
worker-a done
worker-b sees deadline: 2025-01-31 00:00:00
worker-b done

How it works

The ContextVar creates a context variable that is automatically propagated to child tasks when using asyncio. When you call deadline.set(), it stores the value in the current context, and any new task started within that context inherits it. The token returned by set() allows you to reset the context to its previous state with reset(). The try/finally block ensures the context is always restored, even if an exception occurs. This pattern is essential for passing request-scoped data like deadlines, user IDs, or tracing IDs through asynchronous code without threading them as parameters.

Common mistakes

  • Forgetting to reset the context after setting it, causing stale values to leak
  • Assuming context is shared across threads when it's actually per-context (per-thread/per-task)
  • Setting a context variable outside an async context and expecting it to apply to tasks created outside that context

Variations

  1. Use `contextvars.copy_context()` to manually copy and run a context in specific tasks
  2. Use `contextlib.AsyncExitStack` to manage multiple context variable sets and resets cleanly

Real-world use cases

  • Passing a timeout deadline from an API request down through nested async service calls without changing function signatures.
  • Carrying a trace ID through an asyncio-based microservice so all log entries for one request share the same correlation ID.
  • Propagating a user's rate-limit budget through concurrent async workers enforcing quota without global mutable state.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Reliability & rate limiting

Related tutorials and quizzes for this topic.