asyncio 适合大量等待网络或磁盘的 I/O 场景。它通过事件循环在协程之间切换,并不会自动让 CPU 密集计算变快。真正可靠的异步程序必须对并发上限、超时、取消和任务异常做显式设计。

核心原则

  • 只在异步调用链中使用可等待的 I/O 客户端,避免阻塞事件循环。
  • 用 create_task 创建需要并发运行的任务,并确保最终等待或取消每一个任务。
  • 使用 asyncio.timeout 或 wait_for 设置截止时间,避免请求永久悬挂。
  • 用 Semaphore 限制同时访问外部服务的数量,并结合连接池容量设置上限。
  • 任务取消时捕获 CancelledError 仅用于清理,清理后继续抛出取消信号。

推荐的实践步骤

批量请求可以把每个 URL 包装为受 Semaphore 保护的协程,再用 TaskGroup 统一管理。任何子任务异常时,TaskGroup 会取消其余任务并汇总错误。对可重试故障增加有限次数的指数退避,同时为整批任务设置总超时,防止局部重试突破业务时限。

async def fetch_one(client, url, sem):
    async with sem:
        async with asyncio.timeout(3):
            return await client.get(url)

async with asyncio.TaskGroup() as group:
    for url in urls:
        group.create_task(fetch_one(client, url, sem))

示例用于说明实现思路,实际项目还应结合所使用的框架版本、部署环境和业务约束进行调整。重要配置要进入版本管理,并在测试环境验证后再发布。

常见误区

  • 在协程中调用 requests 或 time.sleep,会阻塞整个事件循环。
  • 创建任务后不保存引用也不等待,异常可能丢失并造成资源泄漏。
  • 无限并发会迅速耗尽文件描述符、连接池或第三方接口配额。

上线前检查清单

  • 确认“只在异步调用链中使用可等待的 I/O 客户端,避免阻塞事件循环”已经通过代码审查或运行验证。
  • 确认“用 create_task 创建需要并发运行的任务,并确保最终等待或取消每一个任务”已经通过代码审查或运行验证。
  • 确认“使用 asyncio.timeout 或 wait_for 设置截止时间,避免请求永久悬挂”已经通过代码审查或运行验证。
  • 确认“用 Semaphore 限制同时访问外部服务的数量,并结合连接池容量设置上限”已经通过代码审查或运行验证。
  • 为失败路径、边界条件和回滚方案准备测试或演练记录。
  • 上线后观察错误率、延迟和资源消耗,确认变化符合预期。

总结

Python asyncio 并发编程:任务、超时与限流的关键在于把隐含假设变成可执行的约束,并通过测试、监控和复盘持续验证。先从影响最大的真实场景开始,小步调整并保留回滚能力,通常比一次性大范围改造更安全,也更容易积累可复用的工程经验。