如何从零实现Python异步生成器?同步生成器转异步正确方法
核心问题排查
你写的示例代码没有实现并发,是两个低级错误导致的:
asyncio.sleep(0)前漏写了await:所有异步协程只有被await才会真正执行,没加await的话这行代码只会生成一个闲置的协程对象,完全不会触发事件循环切换,等于白写。- 入口
run()函数只等待了第一个任务,剩余两个被调度到事件循环的任务没有被显式等待,事件循环会在第一个任务跑完后直接退出,剩下的任务甚至没机会执行完。
异步生成器基础规则
不管是从零编写还是改造同步生成器,先记住异步生成器的核心定义:
- 用
async def声明的函数,内部使用yield返回值,它就是异步生成器 - 异步生成器只能通过
async for语法迭代,普通for循环无法消费 - 异步生成器内部可以正常使用
await调用异步逻辑,执行到await点时会主动让出事件循环控制权,让其他协程并发执行
从零实现一个最简单的异步生成器示例:
import asyncio async def async_counter(stop: int): for i in range(stop): # 这里可以替换为任意异步IO操作,比如网络请求、异步读文件 await asyncio.sleep(0.1) yield i
同步生成器转异步生成器的正确方式
首先要明确边界:
如果你的同步生成器内部是CPU密集型逻辑、或者包含阻塞IO操作(比如同步网络请求、同步文件读写),单纯加await切换点是没用的——阻塞逻辑运行时会直接卡住整个事件循环,根本不会触发并发。这种场景需要用
loop.run_in_executor把阻塞逻辑扔到线程池/进程池执行,避免阻塞事件循环。
如果你的同步生成器逻辑本身执行速度很快,只是想在迭代间隙让出事件循环,避免长时间占用线程阻塞其他协程,改造非常简单:每次yield之后加一个await asyncio.sleep(0)即可,这个调用会立刻把控制权交还给事件循环,让其他待运行的协程先执行。
修正后的可并发运行的示例代码
#!/usr/bin/env python3 import asyncio async def run(): # 创建3个并发任务,用gather等待所有任务执行完成 tasks = [ asyncio.ensure_future(run_async_loop()), asyncio.ensure_future(run_async_loop()), asyncio.ensure_future(run_async_loop()) ] await asyncio.gather(*tasks) async def run_async_loop(): async for i in some_async_loop(): print(i) async def some_async_loop(): for i in some_loop(): yield i # 必须加await才会真正触发事件循环切换 await asyncio.sleep(0) def some_loop(): for i in range(10): yield i if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(run())
运行这段代码你会看到三个循环的输出是交替打印的,不再是一个循环跑完才跑下一个,符合预期的并发效果。
阻塞型同步生成器的包装示例
如果你的同步生成器包含阻塞操作,参考下面的包装方式,用线程池执行阻塞逻辑避免卡事件循环:
import asyncio import time # 带阻塞操作的同步生成器示例 def blocking_sync_gen(): for i in range(10): # 模拟阻塞IO,比如同步HTTP请求、同步读大文件 time.sleep(0.1) yield i async def wrapped_async_gen(): loop = asyncio.get_running_loop() gen = blocking_sync_gen() while True: try: # 把next迭代操作扔到默认线程池执行,不阻塞事件循环 val = await loop.run_in_executor(None, next, gen) yield val except StopIteration: break
内容的提问来源于stack exchange,提问作者JonasVautherin
相关产品推荐
相关产品推荐

