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.
Python code
30 linesimport 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
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
- Use `contextvars.copy_context()` to manually copy and run a context in specific tasks
- 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
More from Reliability & rate limiting
- At Least Once with Idempotent Consumer in Python medium
- Build a Rate Limiter Decorator in Python easy
- Build a queue-based admission control system in Python easy
- Chaos Inject Random Failures in Python easy
- Circuit breaker failure threshold count in Python medium
- Exactly Once Processing Dedupe Mock in Python easy
Keep learning
Related tutorials and quizzes for this topic.