【Python 异步编程入门】:彻底弄懂 async&await
目录
1 前言
最近在写爬虫(尤其是多分片ts视频下载)、高并发请求相关的代码时,发现在接触 Python 的async/await时一头雾水:
- 为什么 async 定义的函数直接调用不执行?
- await 到底是干嘛的?
- 异步代码的执行顺序和同步到底有啥区别?
- 遇到 await 程序到底是挂起还是等待?
本篇就用大白话+通俗例子+代码实例,从零讲懂 Python 异步编程的核心:async / await,不讲晦涩术语,新手也能轻松吃透,看完直接上手写异步代码!
2 先搞懂:同步 VS 异步,到底差在哪?
在学 async/await 之前,必须先分清同步和异步的执行逻辑,这是理解异步的基础。
2.1 同步执行(阻塞式)
同步就是按顺序做事,一件做完再做下一件,遇到等待(比如网络请求、sleep、文件读写)时,程序会卡死等待,啥也不干,直到等待结束。
举个生活例子:烧水时站在水壶旁边死等,水开了再去洗菜,中间啥也不做。
代码示例:
import time
# 同步任务1
def task1():
print("任务1开始执行")
time.sleep(2) # 模拟等待2秒(阻塞)
print("任务1执行完毕")
# 同步任务2
def task2():
print("任务2开始执行")
time.sleep(2) # 模拟等待2秒(阻塞)
print("任务2执行完毕")
# 同步执行
if __name__ == '__main__':
start = time.time()
task1()
task2()
end = time.time()
print(f"同步执行总耗时:{end - start:.2f}秒")
执行结果:
任务1开始执行
任务1执行完毕
任务2开始执行
任务2执行完毕
同步执行总耗时:4.01秒
结论:同步是串行执行,总耗时是所有任务等待时间之和,效率极低。
2.2 异步执行(非阻塞式)
异步就是不用死等,遇到等待就切换去做别的事,等待结束后再回来继续执行,同一时间可以处理多个任务,CPU 不会空转。
举个生活例子:点开烧水开关后,不用站着等,直接去洗菜,水开了再回来处理,两件事同时推进。
代码示例(异步基础版):
import asyncio
import time
# 异步任务1(async修饰,变成协程函数)
async def task1():
print("任务1开始执行")
await asyncio.sleep(2) # 异步等待,非阻塞
print("任务1执行完毕")
# 异步任务2
async def task2():
print("任务2开始执行")
await asyncio.sleep(2) # 异步等待,非阻塞
print("任务2执行完毕")
# 异步主函数
async def main():
# 并发执行两个异步任务
await asyncio.gather(task1(), task2())
if __name__ == '__main__':
start = time.time()
asyncio.run(main()) # 启动异步事件循环
end = time.time()
print(f"异步执行总耗时:{end - start:.2f}秒")
等待函数使用 await asyncio.sleep(2) 而不是 time.sleep(2)
执行结果:
任务1开始执行
任务2开始执行
任务1执行完毕
任务2执行完毕
异步执行总耗时:2.01秒
结论:异步是并发执行,总耗时≈最长单个任务时间,效率大幅提升!
3 核心关键字 1:async,到底有什么用?
3.1 async 的作用
async 是用来定义协程函数的关键字,简单来说:
被 async 修饰的函数,不再是普通函数,直接调用不会执行函数体,只会返回一个协程对象(Coroutine Object),必须通过 await/事件循环才能真正执行。
3.2 关键误区:async 函数直接调用不执行!
很多新手踩坑:async 函数直接调用,发现里面的 print、逻辑都不跑,只返回一个奇怪的对象,这是正常现象!
错误示例:
import asyncio
# 定义async协程函数
async def test():
print("我是async函数")
return 666
# 直接调用,不执行!
res = test()
print(res) #<coroutine object test at 0x000001234567890>
正确执行方式:必须放在异步函数里,用 await 调用,或通过 asyncio.run() 启动。
3.3 async 函数总结
- async 修饰 → 协程函数,非普通函数
- 直接调用 → 返回协程对象,不执行代码
- 必须配合 await/事件循环,才能真正运行
4 核心关键字 2:await,彻底讲明白
4.1 await 的通俗理解
你之前理解的完全正确!用一句话总结:
await = 挂起当前任务 + 切换执行其他任务 + 等待结束后回来继续
遇到 await 时,程序不会死等,而是把当前任务挂起,先去执行其他可运行的代码,等 await 的操作完成后,再回到原来的位置继续往下走。
4.2 await 的两大铁律
- 律 1:await 只能用在 async 定义的协程函数里,普通函数用 await 直接报错
- 律 2:await 后面只能跟可等待对象(协程对象、Task 对象、Future 对象),普通函数不能加 await
4.3 await 执行流程(超通俗)
- 执行到
await 任务→ 暂停当前任务 - 切换到事件循环里的其他可执行任务
- 等待的任务完成 → 切回原任务,继续执行后续代码
- 全程非阻塞,CPU 不空闲
4.4 代码拆解 await 执行顺序
import asyncio
async def demo():
print("第一步:执行demo函数")
# 遇到await,挂起demo,去执行其他任务
await asyncio.sleep(1)
# 等待结束,切回demo,继续执行
print("第三步:await等待结束,继续执行")
async def main():
print("第二步:执行main函数,调用demo")
await demo()
print("第四步:所有任务执行完毕")
asyncio.run(main())
执行顺序:
第二步:执行main函数,调用demo
第一步:执行demo函数
第三步:await等待结束,继续执行
第四步:所有任务执行完毕
顺序解析:await demo() 的意思是:main 函数必须等 demo 函数从头到尾全部执行完,才能继续往下走第四步。
在 demo 内部遇到 await asyncio.sleep(1) 时,demo 会被挂起等待 asyncio.sleep(1) 运行完毕并交出控制权,但因为 main 正在 await 它、没有其他任务可执行,程序只能空等 1 秒;等 sleep 结束后 切回demo 继续跑完,然后 main 才会执行第四步。
简单说:await 是“等它跑完”,不是“扔一边不管”。
5 异步执行核心:事件循环与协程
5.1 协程(Coroutine)
被 async 修饰的函数就是协程函数,调用后得到的对象就是协程对象,协程的特点是可以被暂停和恢复,这也是异步能切换任务的核心。
5.2 事件循环(Event Loop)
loop = asyncio.get_event_loop()
异步程序的总指挥,负责:
- 管理所有待执行的协程任务
- 遇到 await 就挂起任务,切换其他任务
- 监听等待任务的完成状态,完成后恢复执行
简单理解:事件循环就是一个“任务调度器”,来回切换协程任务,让程序非阻塞运行。
5.3 异步启动方式(新手必记)
Python 3.7+ 简化了异步启动,直接用 asyncio.run(主协程函数) 即可,自动创建和关闭事件循环,新手不用管复杂的循环创建逻辑。
6 常见坑点总结,新手避坑
- async 函数直接调用,不执行:忘记加 await,或没通过 asyncio. run 启动
- 普通函数加 await 报错:await 只能跟可等待对象,普通函数不能用
- await 放在普通函数里报错:await 必须在 async 函数内部
- 异步 sleep 用成 time. sleep:time. sleep 是同步阻塞,异步里必须用
await asyncio.sleep() - 协程对象没执行完:忘记 await,导致任务没跑完程序就结束
7 总结
最后再用 3 句话,帮你彻底记住 async/await 核心:
- async:定义协程函数,直接调用不执行,返回协程对象
- await:挂起当前任务,切换执行其他任务,等待完成后恢复
- 异步核心:非阻塞、协程切换、事件循环调度,适合 IO 密集型场景(爬虫、网络请求、文件读写)
Python 异步编程看似复杂,其实弄懂执行逻辑和关键字作用,新手也能快速上手,尤其是写高并发爬虫、接口调用时,异步能大幅提升程序效率,赶紧动手试试吧!
更多推荐


所有评论(0)