如何用Python asyncio限制并发?相关技术问题咨询
回答
问题1:更高效地等待所有下载任务完成
你现在用固定等待9秒的方式确实不够灵活,毕竟实际下载时长是随机的,很容易出现等早了任务没跑完或者等久了浪费时间的情况。其实你可以保存所有创建的任务引用,然后用asyncio.gather()或者asyncio.wait()来等待所有任务完成,这才是异步编程里的标准做法。
修改后的main函数可以这样写:
async def main(loop): global count permit_download = asyncio.Event() permit_download.set() downloading_event = asyncio.Event() no_concurrent = 3 i = 0 tasks = [] # 用来存储所有任务引用 while i < 9: if permit_download.is_set(): count += 1 if count >= no_concurrent: permit_download.clear() task = loop.create_task(download(i, permit_download, no_concurrent, downloading_event)) tasks.append(task) # 添加到任务列表 await downloading_event.wait() downloading_event.clear() i += 1 else: await permit_download.wait() await asyncio.gather(*tasks) # 等待所有任务完成
这样就不需要靠猜时长来等待了,asyncio.gather()会自动等到所有传入的任务都执行完毕才继续往下走,完全贴合你的需求。
另外,其实你还可以用**信号量(Semaphore)**来简化并发控制的逻辑,不需要自己维护count和permit_download事件,Semaphore本身就可以限制同时运行的任务数,代码会更简洁:
from random import randint import asyncio async def download(code, semaphore): async with semaphore: wait_time = randint(1, 3) print('downloading {} will take {} second(s)'.format(code, wait_time)) await asyncio.sleep(wait_time) print('downloaded {}'.format(code)) async def main(): no_concurrent = 3 semaphore = asyncio.Semaphore(no_concurrent) tasks = [download(i, semaphore) for i in range(9)] await asyncio.gather(*tasks) if __name__ == '__main__': asyncio.run(main())
用Semaphore的方式不仅去掉了全局变量和复杂的事件控制,还能更优雅地实现最多3个并发下载的需求,同时自然地等待所有任务完成。
问题2:类似JavaScript async库的Python异步工具库
Python生态里有不少专门处理异步模式的优秀库,推荐几个常用的:
- aiojobs:专门用来管理异步任务的库,支持任务的批量创建、取消、超时控制,很适合处理类似你这种需要控制并发数的场景,用法比原生的更简洁。
- tenacity:专注于重试逻辑的库,不管是同步还是异步代码都能轻松添加重试机制,比如下载失败自动重试,配置非常灵活。
- asyncstdlib:提供了很多类似标准库但支持异步的工具函数,比如异步版本的
map、filter、reduce,让你在异步代码里也能像写同步代码一样处理迭代器。 - anyio:一个兼容性很强的异步库,同时支持asyncio和trio两种异步框架,提供了很多高级的异步工具,比如任务组、超时管理、并发限制等,适合需要跨框架兼容的场景。
这些库都能帮你简化异步代码的编写,解决常见的异步编程痛点,和JavaScript里的async库功能类似。
内容的提问来源于stack exchange,提问作者Shridharshan
相关产品推荐
相关产品推荐

