Python如何在for循环中异步执行函数实现非阻塞调用
问题原因
你写的asyncio.gather仍然串行执行,核心原因只有一个:协程本身是单线程协作式调度的,只要你的bar协程内部没有通过await主动让出事件循环控制权,它就会一直霸占线程直到执行完毕,其他协程根本没有启动的机会,自然表现为串行。
常见触发场景:
- 只是给普通同步函数加了
async关键字,内部没有任何异步等待逻辑,执行全程不会让出控制权 bar内部的耗时操作是同步阻塞的,比如用time.sleep、requests发请求、同步读写文件、CPU密集计算,这些操作不会触发协程挂起,会直接堵死事件循环- 顺带提一句你原始代码里的笔误:
newItems.append[newItem]写法错误,列表append是方法调用,应该写成newItems.append(newItem),这个不影响并发逻辑但会直接抛错。
解决方案
根据你bar函数的耗时类型选对应方案即可:
场景1:bar的耗时来自IO操作(网络请求、文件读写等)
直接把bar内部的同步IO替换为对应的异步实现,确保耗时等待的位置有await让出控制权,asyncio.gather就会自动调度协程并发执行。
示例代码(用asyncio.sleep模拟异步IO等待):
import asyncio import time async def bar(item): # 替换为你的实际异步IO逻辑,比如aiohttp请求、aiofiles文件读写 await asyncio.sleep(1) # 执行到这里会挂起当前协程,事件循环可以启动其他待运行的协程 return f"handled_{item}" async def main(): items = range(5) tasks = [bar(item) for item in items] return await asyncio.gather(*tasks) if __name__ == "__main__": start = time.time() result = asyncio.run(main()) print(result) print(f"执行耗时:{time.time() - start:.2f}s") # 5个任务各等待1s,并发执行总耗时约1s,而非串行的5s
注意:不要在协程内部直接调用同步阻塞方法,否则会直接堵死事件循环,并发完全失效。
场景2:bar是同步逻辑(同步IO/CPU密集计算,不想改写内部实现)
协程无法直接调度同步阻塞代码实现并发,需要借助线程池/进程池把同步任务扔到独立的执行单元运行,再通过asyncio封装成可等待对象交给gather调度:
- 如果是同步IO类耗时,用
ThreadPoolExecutor即可 - 如果是CPU密集计算类耗时,用
ProcessPoolExecutor避开GIL限制
示例代码:
import asyncio import time from concurrent.futures import ThreadPoolExecutor # 保留原始同步bar函数,不需要加async关键字 def bar(item): time.sleep(1) # 模拟同步耗时操作,不管是同步IO还是CPU计算都适用 return f"handled_{item}" async def main(): items = range(5) loop = asyncio.get_running_loop() # 配置线程池,可根据实际场景调整max_workers数量 with ThreadPoolExecutor(max_workers=5) as executor: tasks = [loop.run_in_executor(executor, bar, item) for item in items] return await asyncio.gather(*tasks) if __name__ == "__main__": start = time.time() result = asyncio.run(main()) print(result) print(f"执行耗时:{time.time() - start:.2f}s") # 同样总耗时约1s,并发生效
常见误区提醒
不要觉得给函数加了async def前缀就自动变成异步执行了,async只是标记函数为协程函数,只有当协程内部await的对象会真正触发协程挂起、把控制权交还给事件循环时,才会产生并发效果。如果协程内部全是同步计算逻辑,哪怕用gather包裹,本质还是按顺序串行执行。
内容的提问来源于stack exchange,提问作者plynch714
相关产品推荐
相关产品推荐

