前言
适用版本: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...>
注意:
- 协程对象必须交给事件循环调度才能执行;
- 普通函数调用不会触发异步执行。
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())
注意:
obj.run_task()只生成协程对象,不执行;- 必须搭配 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、异步代码依旧串行、无并发
三大核心原因:
- 使用了
time.sleep同步阻塞; - 顺序 await,没有用
create_task/gather; - 多次调用
asyncio.run(),多个独立循环。
七、异步使用场景
1、✅ 适合异步(IO 密集)
- 网络请求(爬虫、接口调用)
- 数据库读写、Redis 操作
- 文件读写、等待第三方响应
2、❌ 不适合异步(CPU 密集)
大量数值计算、循环解析、加密解密 → 建议使用多进程
八、终极记忆口诀(速成)
async def造协程,调用不执行await是等待,顺序串行跑create_task后台挂,真正并发妙asyncio.sleep专用,time.sleep必翻车- run 只写一次,循环全局套
- 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.)