如何解决asyncio中loop.run_until_complete()调用阻塞的并发问题?
解决asyncio中run_until_complete阻塞导致任务串行的问题
嘿,这个问题我之前踩过坑!你现在的代码里,三次调用loop.run_until_complete()是串行执行的——每次调用都会阻塞当前线程,直到传入的asyncio.gather()任务全部完成,才会继续执行下一行。这就导致你本来想并发的三组任务,实际上是一组跑完再跑下一组,完全没用到asyncio的并发优势。
核心问题理解
loop.run_until_complete()本身是同步阻塞的方法,它的作用就是让主线程等待传入的协程执行完毕,所以你没法让它“异步运行”。要实现真正的并发,你需要把所有需要同时执行的协程,交给事件循环一次性调度,而不是分多次调用阻塞方法。
两种可行的解决方案
方案1:合并所有协程到一个大的gather里
如果不需要严格区分x、y、z三个批次的执行结果分组,可以直接把所有async_func协程合并到一个列表,用一次gather和一次run_until_complete执行:
import asyncio async def async_func(item): # 模拟耗时IO任务,比如网络请求、文件读写 await asyncio.sleep(1) return item * 2 data_x = [1, 2, 3] data_y = [4, 5, 6] data_z = [7, 8, 9] loop = asyncio.get_event_loop() # 收集所有要执行的协程 all_coroutines = list(map(async_func, data_x)) + list(map(async_func, data_y)) + list(map(async_func, data_z)) # 一次性并发执行所有协程 all_results = loop.run_until_complete(asyncio.gather(*all_coroutines)) # 拆分回x、y、z的结果 x = all_results[:len(data_x)] y = all_results[len(data_x):len(data_x)+len(data_y)] z = all_results[len(data_x)+len(data_y):] print("x的结果:", x) print("y的结果:", y) print("z的结果:", z)
方案2:并发执行三个gather任务
如果你需要保持x、y、z三个批次的独立gather(比如每个批次的结果需要单独处理),可以把三个asyncio.gather()本身作为协程,再用一个外层的gather来并发执行这三个任务:
loop = asyncio.get_event_loop() # 创建三个gather协程对象(注意这里不要立即执行) gather_x = asyncio.gather(*map(async_func, data_x)) gather_y = asyncio.gather(*map(async_func, data_y)) gather_z = asyncio.gather(*map(async_func, data_z)) # 一次性并发执行这三个gather任务 x, y, z = loop.run_until_complete(asyncio.gather(gather_x, gather_y, gather_z)) print("x的结果:", x) print("y的结果:", y) print("z的结果:", z)
更简洁的Python 3.7+写法
如果你用的是Python 3.7及以上版本,推荐使用asyncio.run()来自动管理事件循环,代码会更简洁:
async def main(): gather_x = asyncio.gather(*map(async_func, data_x)) gather_y = asyncio.gather(*map(async_func, data_y)) gather_z = asyncio.gather(*map(async_func, data_z)) # 并发执行三个gather并获取结果 return await asyncio.gather(gather_x, gather_y, gather_z) x, y, z = asyncio.run(main()) print("x的结果:", x) print("y的结果:", y) print("z的结果:", z)
关键总结
- 不要多次调用
loop.run_until_complete(),因为它是阻塞的,会破坏并发。 - 所有需要并发执行的协程(包括gather任务),都要交给事件循环一次性调度,这样事件循环才能在IO等待时切换其他任务,实现真正的异步并发。
内容的提问来源于stack exchange,提问作者Blaszard
相关产品推荐
相关产品推荐

