Python异步编程的发展历程可以看作是一部解决I/O密集型应用性能问题的进化史。早期的生成器(Generator)和yield语法已经为异步编程埋下了伏笔,而StopIteration异常则是理解这一演进过程的重要切入点。
在Python 3.3之前,生成器函数通过raise StopIteration来终止迭代。这个看似简单的机制实际上为后来的协程(Coroutine)奠定了基础。当生成器函数执行到return语句时,解释器会自动抛出StopIteration,其value属性携带返回值。这个特性在PEP 380引入yield from语法后变得尤为重要。
关键提示:Python 3.7后,生成器中的return值会直接触发StopIteration,而不再需要显式抛出。这是理解现代asyncio的重要背景知识。
同步编程模型在处理高并发网络请求时面临的根本问题是阻塞调用。当一个线程执行I/O操作时,整个线程会被操作系统挂起,导致宝贵的系统资源被闲置。异步编程通过"非阻塞I/O+事件循环"的架构解决了这个问题,而理解从StopIteration到asyncio的演进过程,能帮助我们掌握这一技术范式的精髓。
生成器函数通过yield关键字实现执行暂停和值产出。每次调用next()时,生成器从上次暂停的位置继续执行,直到遇到下一个yield。这个机制已经具备了协程的雏形:
python复制def simple_coroutine():
print("-> coroutine started")
x = yield # 暂停点1
print("-> coroutine received:", x)
y = yield x + 10 # 暂停点2
print("-> coroutine received:", y)
coro = simple_coroutine()
next(coro) # 启动生成器,运行到第一个yield
coro.send(20) # 发送值到x,运行到第二个yield
coro.send(30) # 发送值到y,生成器终止
yield from语法在Python 3.3中引入,它解决了生成器嵌套时的繁琐调用问题。其核心功能包括:
Python 3.4通过asyncio模块正式引入了原生协程支持。这个版本使用@asyncio.coroutine装饰器和yield from语法定义协程:
python复制import asyncio
@asyncio.coroutine
def old_style_coroutine():
yield from asyncio.sleep(1)
return "done"
Python 3.5引入了async/await语法,使协程的定义更加清晰:
python复制async def modern_coroutine():
await asyncio.sleep(1)
return "done"
重要区别:生成器式协程和原生协程在类型上是不同的。使用inspect.iscoroutine()可以检测原生协程,而inspect.isgenerator()检测生成器。
事件循环(Event Loop)是asyncio的核心,它本质上是一个高效的任务调度器。其工作流程可以概括为:
创建自定义事件循环的典型模式:
python复制import selectors
import types
class SimpleEventLoop:
def __init__(self):
self._selector = selectors.DefaultSelector()
self._tasks = []
def create_task(self, coro):
task = types.SimpleNamespace()
task.coro = coro
task._result = None
self._tasks.append(task)
return task
def run_until_complete(self, coro):
task = self.create_task(coro)
try:
while True:
events = self._selector.select(0.1)
for key, mask in events:
callback = key.data
callback()
if not self._tasks:
break
current = self._tasks.pop(0)
try:
next(current.coro)
self._tasks.append(current)
except StopIteration as e:
current._result = e.value
finally:
self._selector.close()
Task对象是对协程的封装,它继承自Future。Future代表一个尚未完成的计算结果,其核心方法包括:
set_result(result):标记Future为完成状态set_exception(exception):标记Future为失败状态add_done_callback(fn):添加完成回调任务调度的典型场景:
python复制async def fetch_data():
print("Start fetching")
await asyncio.sleep(2)
print("Done fetching")
return {"data": 1}
async def main():
task1 = asyncio.create_task(fetch_data())
task2 = asyncio.create_task(fetch_data())
print("Main continues")
await asyncio.sleep(1)
print("Main after sleep")
result = await task1
print(f"Result: {result}")
asyncio.run(main())
Python 3.7引入了异步上下文管理器协议(__aenter__和__aexit__),配合async with使用:
python复制class AsyncDatabaseConnection:
async def __aenter__(self):
self.conn = await connect_to_db()
return self.conn
async def __aexit__(self, exc_type, exc, tb):
await self.conn.close()
async def get_user(user_id):
async with AsyncDatabaseConnection() as conn:
return await conn.execute("SELECT * FROM users WHERE id = ?", user_id)
Python 3.6引入了异步生成器(async for和async yield):
python复制async def async_counter(n):
for i in range(n):
yield i
await asyncio.sleep(0.1)
async def main():
async for num in async_counter(5):
print(num)
任务批处理:使用gather代替顺序await
python复制# 不佳实践
result1 = await query1()
result2 = await query2()
# 优化方案
result1, result2 = await asyncio.gather(query1(), query2())
限制并发量:使用信号量控制资源使用
python复制sem = asyncio.Semaphore(10)
async def limited_task():
async with sem:
return await expensive_operation()
取消传播:正确处理任务取消
python复制async def resilient_task():
try:
await long_operation()
except asyncio.CancelledError:
await cleanup()
raise
阻塞事件循环:
python复制async def bad_example():
# 同步阻塞调用会冻结整个事件循环
time.sleep(1) # 错误!
await asyncio.sleep(1) # 正确
未等待协程:
python复制async def main():
coro = fetch_data() # 没有await,协程不会执行
await asyncio.sleep(1)
错误的任务创建:
python复制async def flawed_design():
# 直接创建Task而不保存引用可能导致任务被垃圾回收
asyncio.create_task(background_job()) # 危险!
事件循环慢回调检测:
python复制loop = asyncio.get_event_loop()
loop.slow_callback_duration = 0.1 # 秒
任务树可视化:
python复制def show_tasks():
tasks = asyncio.all_tasks()
for task in tasks:
print(f"{task.get_name()}: {task.get_coro()}")
异步REPL:
python复制python -m asyncio
调试心得:在复杂应用中,为关键任务设置明确的名称(
task.set_name())能极大简化调试过程。同时,使用asyncio.run()而非手动管理事件循环可以避免很多资源泄漏问题。
结构化并发:使用asyncio.TaskGroup(Python 3.11+)管理任务生命周期
python复制async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch_data())
task2 = tg.create_task(process_data())
错误处理策略:
python复制async def robust_operation():
try:
result = await unreliable_api()
except APIError as e:
result = await fallback_source()
except asyncio.TimeoutError:
raise ServiceUnavailable()
else:
return normalize(result)
测试模式:
python复制@pytest.mark.asyncio
async def test_async_code():
result = await function_under_test()
assert result == expected_value
与线程池集成:
python复制async def run_in_thread(func):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, func)
在实际项目中,异步编程最关键的思维转变是从"顺序执行"到"状态机管理"。每个await点都代表一个可能的事件循环迭代,理解这一点才能编写出高效可靠的异步代码。