Python asyncio 异步编程全套学习文档(零基础完整版)

前言

适用版本:Python 3.0+(主流版本)

核心定位 :asyncio 是单线程并发 方案,专门解决 IO 密集型任务(网络请求、数据库、文件读写、等待响应),不适合 CPU 密集型计算。

一、异步核心四大基础概念

1、协程函数 & 协程对象

async def 定义的函数 = 协程函数。

调用协程函数,不会执行函数体,只会返回一个协程对象(任务说明书)。

python 复制代码
import asyncio

# 定义协程函数
async def demo():
    print("协程执行")

# 仅仅生成协程对象,代码不运行!
coro = demo()
print(coro)  # <coroutine object demo at 0x...>

注意:

  1. 协程对象必须交给事件循环调度才能执行;
  2. 普通函数调用不会触发异步执行。

2、事件循环(Event Loop)

异步的总管家,唯一核心调度器:

  • 维护所有待执行的协程/任务
  • 遇到 await挂起当前任务,让出 CPU,执行其他任务
  • IO 等待时不占用 CPU,极大提升 IO 场景效率

入口函数(唯一推荐):asyncio.run()

作用:自动创建事件循环 → 执行任务 → 自动关闭循环。

黄金规则:一个程序只允许调用一次 asyncio.run(),多次调用会开启多个独立循环,无法并发。

3、await 关键字(核心)

作用:阻塞当前协程,等待对应任务执行完毕,再继续往下执行。

语法限制:只有用在 async 标记的函数内部才有用!

可等待对象:协程、Task、Future

python 复制代码
async def task1():
    await asyncio.sleep(2)  # 异步挂起,让出循环
    print("task1 执行完毕")

async def main():
    await task1()  # 串行等待任务完成

asyncio.run(main())

重点:单独使用 await 是串行执行,不是并发!

4、Task 任务(实现并发的关键)

作用:将协程包装为后台任务,提交给事件循环,自动并发执行。

创建方式:asyncio.create_task(协程对象)

python 复制代码
import asyncio

async def task1():
    await asyncio.sleep(3)
    print("task1 完成")

async def task2():
    print("task2 完成")

async def main():
    # 打包为后台任务,并发执行
    t1 = asyncio.create_task(task1())
    t2 = asyncio.create_task(task2())

    # 等待所有任务结束
    await t1
    await t2

asyncio.run(main())

输出顺序:task2 立刻执行 → 等待3秒 → task1 执行,实现真正并发。

二、串行 VS 并发

串行(serial):一个 CPU 上,按顺序完成多个任务;

并行(parallelism):为每个任务分配 CPU,多个任务共同执行;

并发(concurrency):一个 CPU 采用时间片管理方式,交替的处理多个任务。一般是任务数多于 CPU 数量,导致 CPU 无法分配,通过操作系统的各种任务调度算法,实现用多个任务"一起"执行。

1、串行执行(效率低)

直接依次 await,一个完成再执行下一个:

python 复制代码
async def main():
    await task1()  # 等3秒
    await task2()  # 再执行
# 总耗时:3s+

2、并发执行(效率高)

先创建所有 Task,再统一 await:

python 复制代码
async def main():
    t1 = asyncio.create_task(task1())
    t2 = asyncio.create_task(task2())
    await t1
    await t2
# 总耗时:仅最长任务的时间 3s

3、批量并发神器:asyncio.gather

无需手动创建 Task,一键批量并发,且自动收集返回值

python 复制代码
async def main():
    res = await asyncio.gather(
        task1(),
        task2()
    )
    print(res)  # 接收所有任务的返回结果

三、异步最大坑:同步阻塞 VS 异步等待

1、绝对禁止:异步中使用 time.sleep()

time.sleep(n)同步阻塞,会直接卡死整个事件循环,所有异步任务全部暂停,异步彻底失效。

python 复制代码
# ❌ 错误写法!阻塞整个循环
async def bad_func():
    import time
    time.sleep(3)

2、标准异步等待

python 复制代码
# ✅ 正确写法!让出事件循环,支持并发
async def good_func():
    await asyncio.sleep(3)

3、必须调用同步代码的解决方案

遇到旧版同步阻塞代码(数据库、文件、time.sleep),用 asyncio.to_thread 丢到子线程执行,不阻塞事件循环:

python 复制代码
import time

async def sync_wrap():
    await asyncio.to_thread(time.sleep, 3)
    print("同步任务执行完成")

四、类中的异步方法(项目最常用)

类中支持:异步实例方法、异步类方法、异步静态方法,写法固定。

python 复制代码
import asyncio

class AsyncDemo:
    # 异步实例方法(最常用)
    async def run_task(self):
        await asyncio.sleep(2)
        print("实例异步方法执行完成")

    # 异步类方法
    @classmethod
    async def cls_task(cls):
        await asyncio.sleep(1)
        print("异步类方法执行完成")

    # 异步静态方法
    @staticmethod
    async def sta_task():
        await asyncio.sleep(1)
        print("异步静态方法执行完成")

# 统一调用方式
async def main():
    obj = AsyncDemo()
    await obj.run_task()
    await AsyncDemo.cls_task()
    await AsyncDemo.sta_task()

asyncio.run(main())

注意:

  1. obj.run_task() 只生成协程对象,不执行;
  2. 必须搭配 await 或 create_task 使用。

五、异步并发限流

无限创建 Task 会导致内存溢出、接口限流,使用信号量 Semaphore 限制最大并发数:

python 复制代码
import asyncio

# 限制最大同时5个任务
sem = asyncio.Semaphore(5)

async def limited_task(num):
    async with sem:  # 自动抢占、释放名额
        await asyncio.sleep(1)
        print(f"任务{num}执行完成")

async def main():
    # 批量生成协程对象,并不执行
    tasks = [limited_task(i) for i in range(20)]
    # 将生成的协程对象解包,放入 gather 中并发执行
    await asyncio.gather(*tasks)

asyncio.run(main())

总结:

  • limited_task(i) → 制造协程对象,函数逻辑不执行
  • 协程交给 gather / create_task / await → 交给事件循环,函数体才真正跑起来,才去抢 sem 名额。

六、所有高频报错 & 解决方案

1、RuntimeWarning: coroutine was never awaited

原因:定义了协程对象,但没有 await / 没有创建 Task,代码未执行。

解决:必须交给事件循环调度。

2、RuntimeError: asyncio.run() cannot be called from a running event loop

原因:事件循环内部再次调用 asyncio.run()

解决:全程只使用一次 run(),内部全部用 await / Task

3、异步代码依旧串行、无并发

三大核心原因:

  1. 使用了 time.sleep 同步阻塞;
  2. 顺序 await,没有用 create_task / gather
  3. 多次调用 asyncio.run(),多个独立循环。

七、异步使用场景

1、✅ 适合异步(IO 密集)

  • 网络请求(爬虫、接口调用)
  • 数据库读写、Redis 操作
  • 文件读写、等待第三方响应

2、❌ 不适合异步(CPU 密集)

大量数值计算、循环解析、加密解密 → 建议使用多进程

八、终极记忆口诀(速成)

  1. async def 造协程,调用不执行
  2. await 是等待,顺序串行跑
  3. create_task 后台挂,真正并发妙
  4. asyncio.sleep 专用,time.sleep 必翻车
  5. run 只写一次,循环全局套
  6. IO 用异步,CPU 用多进程

九、完整综合实战模板(项目直接复用)

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio

class AsyncWork:
    async def work1(self):
        print("任务1开始")
        await asyncio.sleep(3)
        print("任务1结束")
        return "任务1完成"

    async def work2(self):
        print("任务2开始")
        await asyncio.sleep(1)
        print("任务2结束")
        return "任务2完成"

async def main():
    obj = AsyncWork()
    # 并发执行
    t1 = asyncio.create_task(obj.work1())
    t2 = asyncio.create_task(obj.work2())

    res1 = await t1
    res2 = await t2
    print(res1, res2)

if __name__ == '__main__':
    asyncio.run(main())

十、练习

1. 基础:协程、await 串行

编写协程函数 say_hello(name, delay),接收名字和延迟秒数,内部 await asyncio.sleep(delay),打印 hello {name}

在 main 中,串行依次调用:

python 复制代码
say_hello("A",2)
say_hello ("B",1)

运行程序,观察总耗时。

python 复制代码
import asyncio

async def say_hello(name, delay):
    await asyncio.sleep(delay)
    print(f"Hello, {name}!")

async def main():
    # 串行调用,await
    await say_hello("A", 1)
    await say_hello("B", 2)

asyncio.run(main())  # 共执行 3s+

2. 并发 create_task

基于练习 1 的函数,改用 create_task 实现并发执行上面两个任务。

思考:串行总耗时 vs 并发总耗时分别大概多少?

python 复制代码
import asyncio

async def say_hello(name, delay):
    await asyncio.sleep(delay)
    print(f"Hello, {name}!")

async def main():
    # 该并发执行
    t1 = asyncio.create_task(say_hello("A", 1))
    t2 = asyncio.create_task(say_hello("B", 2))
    await t1
    await t2

asyncio.run(main())  # 共执行 2s+

3. asyncio.gather + 获取返回值

修改say_hello,让函数 return 返回字符串 f"{name}完成" 使用 asyncio.gather 并发执行 3 个任务:

python 复制代码
say_hello ("张三",3)
say_hello ("李四",2)
say_hello ("王五",1)

拿到 gather 返回的结果列表并打印。

python 复制代码
async def say_hello(name, delay):
    await asyncio.sleep(delay)
    return f"{name}完成"

async def main():
    # 批量并发执行
    await  asyncio.gather(
        say_hello("张三", 3),
        say_hello("李四", 2),
        say_hello("王五", 1)
    )

asyncio.run(main())

4. 类中的异步方法

定义类 TaskJob

  • 实例属性 name
  • 异步实例方法 run(self, sleep_sec):打印 {self.name} 开始,休眠sleep_sec秒,打印{self.name} 结束

创建两个实例:

python 复制代码
obj1 = TaskJob (name="任务 X")
obj2 = TaskJob (name="任务 Y")

将两个对象的 run 方法并发执行,sleep_sec 分别设置 3、1。

python 复制代码
class TaskJob:
    def __init__(self, name):
        self.name = name

    async def run(self, sleep_sec):
        print(f"{self.name} 开始")
        await asyncio.sleep(sleep_sec)
        print(f"{self.name} 结束")

async def main():
    obj1 = TaskJob(name="任务 X")
    obj2 = TaskJob(name="任务 Y")
    await asyncio.create_task(obj1.run(3))
    await asyncio.create_task(obj2.run(1))

asyncio.run(main())

5. 信号量限流实战

编写任务函数 do_work(n),打印执行任务{n}await asyncio.sleep(1)

现在要一次性发起 10 个任务,同一时刻最多允许 3 个任务并发,使用asyncio.Semaphore实现。

python 复制代码
import asyncio

# 限制最大同时5个任务
sem = asyncio.Semaphore(5)

async def limited_task(num):
    async with sem:  # 自动抢占、释放名额
        await asyncio.sleep(1)
        print(f"任务{num}执行完成")

async def main():
    # 批量生成协程对象,并不执行
    tasks = [limited_task(i) for i in range(20)]
    # 将生成的协程对象解包,放入 gather 中并发执行
    await asyncio.gather(*tasks)

asyncio.run(main())

(Note: May contain AI-generated content.)

相关推荐
钱栈up1 小时前
Mac 开发机一键发版不用切环境:我这样改造了团队的后端部署脚本
运维·python·mac
coder_Eight2 小时前
从 3 天到 8 分钟:我如何把垂直科普内容做成了自动化流水线
python·ai编程
水獭比特2 小时前
MCP SDK v2 迁移别只改依赖:先把 FastMCP 3/4 拆成两条测试线
人工智能·python
在线考试系统推荐2 小时前
2026年7月最新实测:三大考试软件导入试题能力对比
人工智能·学习·系统架构
l1258652 小时前
# RAG多轮对话检索设计:Query重写如何让“那它呢“变成完整问题
前端·数据库·人工智能·python·算法·fastapi·milvus
小弥儿2 小时前
GitHub今日热榜 | 2026-08-20:Agent 上下文数据库接棒
数据库·学习·开源·github
sunoo-2292 小时前
【C 语言标准 IO 入门】第二天学习笔记:文件操作核心函数 + 实战案例 + 踩坑合集
linux·笔记·vscode·学习
MartinYeung52 小时前
[论文学习]MPMA:针对模型上下文协议的偏好操纵攻击
人工智能·学习·安全
dorky-org2 小时前
03-Python基础
python·具身智能