71 lines
2.7 KiB
Markdown
71 lines
2.7 KiB
Markdown
---
|
||
title: "Python Async: Beyond the Basics"
|
||
date: 2026-05-12T11:00:00+08:00
|
||
draft: false
|
||
poet: |
|
||
They also serve who only stand and wait.
|
||
——John Milton,英国,1608–1674
|
||
description: "Understanding the event loop, writing proper async context managers, and avoiding the most common pitfalls with asyncio.gather and cancel scopes."
|
||
tags:
|
||
- Python
|
||
- Async
|
||
- Programming
|
||
---
|
||
|
||
Async Python is no longer optional if you're doing any kind of I/O-bound work. Beyond the basics of `async`/`await`, understanding the event loop helps debug elusive issues.
|
||
|
||
## The Event Loop in Practice
|
||
|
||
The event loop runs one coroutine at a time, switching at `await` points. This is cooperative multitasking — a coroutine that never awaits blocks everything else. If you have CPU-bound work inside an async function, offload it to a thread pool with `asyncio.to_thread()`.
|
||
|
||
```python
|
||
import asyncio
|
||
|
||
# Bad: blocks the event loop
|
||
async def bad():
|
||
result = heavy_computation() # CPU-bound, no await
|
||
return result
|
||
|
||
# Good: offloads to a thread
|
||
async def good():
|
||
result = await asyncio.to_thread(heavy_computation)
|
||
return result
|
||
```
|
||
|
||
## Async Context Managers
|
||
|
||
Writing proper async context managers is essential for managing connections and locks:
|
||
|
||
```python
|
||
class AsyncConnection:
|
||
async def __aenter__(self):
|
||
self.conn = await create_connection()
|
||
return self.conn
|
||
|
||
async def __aexit__(self, *args):
|
||
await self.conn.close()
|
||
```
|
||
|
||
The `contextlib.asynccontextmanager` decorator makes this even cleaner for simple cases.
|
||
|
||
## Common Pitfalls with `asyncio.gather`
|
||
|
||
`gather` runs tasks concurrently, but one failing task doesn't cancel the others by default. If you need all-or-nothing semantics, use `asyncio.TaskGroup` (Python 3.11+):
|
||
|
||
```python
|
||
async with asyncio.TaskGroup() as tg:
|
||
t1 = tg.create_task(fetch(url1))
|
||
t2 = tg.create_task(fetch(url2))
|
||
# Both succeeded, or both were cancelled
|
||
```
|
||
|
||
## Cancel Scopes
|
||
|
||
Cancellation in asyncio is cooperative: `asyncio.CancelledError` is raised at the next `await`. If your coroutine catches and suppresses it, the task becomes uncancellable — usually a bug. Use `asyncio.shield()` sparingly to protect critical cleanup code.
|
||
|
||
## Structured Concurrency
|
||
|
||
The trend across languages (Trio in Python, Kotlin coroutines, Swift concurrency) is toward structured concurrency: every task has a clear parent scope that determines its lifetime. `TaskGroup` is Python's step in this direction. Embrace it over bare `create_task()` calls.
|
||
|
||
Async Python has matured significantly. Most of the old footguns have been addressed, but the fundamentals — understand the event loop, respect cancellation, and structure your concurrency — remain essential.
|