如何基于Asyncio实现带异步生成器的并发生产者-消费者流
解决方案:异步并发处理AsyncGenerator输入并实时产出结果
要实现对每个item的slow_task并发处理,且结果就绪即产出、无需保持输入顺序,可以利用asyncio.create_task和asyncio.as_completed实现,无需复杂的生产者/消费者队列(队列方案也可行,但前者更简洁)。
优化后的代码实现
from collections.abc import AsyncGenerator import asyncio async def input_gen() -> AsyncGenerator[str, None]: '''a simple generator that yields strings''' for char in "abc123xyz789": await asyncio.sleep(0.1) yield char async def slow_task(item: str) -> str: '''simulate a slow task that operates on a single item''' await asyncio.sleep(0.5) return f"{item}_loaded" async def my_gen() -> AsyncGenerator[str, None]: '''并发处理每个item,结果就绪即产出''' tasks = set() # 一边读取输入,一边提交任务并处理已完成的结果 async for item in input_gen(): task = asyncio.create_task(slow_task(item)) tasks.add(task) # 迭代已完成的任务,实时产出结果 for done_task in asyncio.as_completed(tasks.copy()): result = await done_task yield result tasks.remove(done_task) # 输入耗尽后,处理剩余未完成的任务 for done_task in asyncio.as_completed(tasks): result = await done_task yield result # 测试执行 async def main(): results = [x async for x in my_gen()] print(results) if __name__ == "__main__": asyncio.run(main())
核心逻辑说明
- 并发任务提交:每从
input_gen拿到一个item,就用asyncio.create_task将slow_task(item)包装成异步任务并加入集合,实现多任务并行执行。 - 实时结果产出:通过
asyncio.as_completed监控任务集合,一旦有任务完成就立即获取结果并yield,满足“就绪即输出”的要求。 - 收尾处理:当
input_gen遍历结束后,继续处理剩余未完成的任务,确保所有结果都能被产出。
效率对比
原串行代码总耗时约7.2秒(12个item × (0.1秒输入间隔 + 0.5秒任务耗时)),优化后的并发代码总耗时约1.6秒(最后一个item的输入间隔1.1秒 + 0.5秒任务耗时),效率提升明显。
备选队列方案(适用于需要并发数控制的场景)
如果需要限制并发任务数量(避免系统资源耗尽),可以用asyncio.Queue实现生产者-消费者模式:
from collections.abc import AsyncGenerator import asyncio async def input_gen() -> AsyncGenerator[str, None]: for char in "abc123xyz789": await asyncio.sleep(0.1) yield char async def slow_task(item: str) -> str: await asyncio.sleep(0.5) return f"{item}_loaded" async def consumer(input_q: asyncio.Queue, output_q: asyncio.Queue): while True: item = await input_q.get() try: result = await slow_task(item) await output_q.put(result) finally: input_q.task_done() async def my_gen(concurrency: int = 5) -> AsyncGenerator[str, None]: input_q = asyncio.Queue() output_q = asyncio.Queue() # 启动指定数量的消费者任务 consumers = [asyncio.create_task(consumer(input_q, output_q)) for _ in range(concurrency)] # 生产者:将输入生成器的item放入队列 async def producer(): async for item in input_gen(): await input_q.put(item) await input_q.join() # 等待所有输入任务被处理 await output_q.put(None) # 发送结束信号 asyncio.create_task(producer()) # 从输出队列拿结果,直到收到结束信号 while True: result = await output_q.get() if result is None: break yield result # 关闭消费者任务 for task in consumers: task.cancel() await asyncio.gather(*consumers, return_exceptions=True) async def main(): results = [x async for x in my_gen()] print(results) if __name__ == "__main__": asyncio.run(main())
该方案通过concurrency参数限制并发数,适合输入量极大的场景。
内容的提问来源于stack exchange,提问作者jhamman
相关产品推荐
相关产品推荐

