Python中关于async/await的用法与常见疑问
对于协程, 一些使用装饰器或者队列的实例基本都能理解, 但是这个 async/await 看了很多实例也不太懂该怎么理解. 网上 90%的教程的例子大概基本都是这样的:
async def func():
print("start...")
r = await asyncio.sleep(5)
print("end...")
这个 asyncio.sleep()从一开始就定义是非阻塞的, 问题是它到底是通过什么方式实现非阻塞的呢? 换成我自己的处理函数, 这个函数应该用什么方式协程非阻塞的?
Python中关于async/await的用法与常见疑问
asyncio.sleep 就是阻塞的,这里是模拟 IO 等阻塞过程
Python中async/await的核心用法与常见疑问解答
async/await是Python异步编程的核心语法,用于编写单线程并发代码。直接上代码看本质:
import asyncio
async def fetch_data(task_id, delay):
"""模拟一个异步IO操作,比如网络请求"""
print(f'任务 {task_id} 开始,需要 {delay} 秒')
await asyncio.sleep(delay) # 非阻塞等待
print(f'任务 {task_id} 完成')
return f'任务 {task_id} 的结果'
async def main():
# 创建多个任务并行执行
tasks = [
fetch_data(1, 2),
fetch_data(2, 1),
fetch_data(3, 3)
]
# 等待所有任务完成,按完成顺序返回结果
results = await asyncio.gather(*tasks)
print('所有任务完成:', results)
# Python 3.7+ 的启动方式
asyncio.run(main())
关键点解析:
- async def:声明一个协程函数,调用它不会立即执行,而是返回一个协程对象
- await:只能用在async函数内部,表示“挂起当前协程,等待后面的awaitable对象完成”
- asyncio.run():Python 3.7+推荐的入口点,管理事件循环
常见疑问解答:
Q: await后面能跟什么? A: 主要是三种:协程对象(async函数调用返回)、Task对象(asyncio.create_task创建)、Future对象。不能跟普通函数。
Q: 为什么要用asyncio.create_task()? A: 它把协程包装成Task,让协程在事件循环中调度,实现并发:
async def concurrent_example():
task1 = asyncio.create_task(fetch_data(1, 2))
task2 = asyncio.create_task(fetch_data(2, 1))
# 两个任务已经开始并行执行
await asyncio.sleep(0.5) # 中间可以干别的
result1 = await task1
result2 = await task2
Q: async/await和线程有什么区别? A: 协程是单线程内的并发,通过事件循环调度,适合IO密集型操作;线程是多核并行,适合CPU密集型操作,但有GIL限制。
Q: 普通函数里能调用async函数吗? A: 不能直接调用,需要用asyncio.run()或在已有事件循环中用run_until_complete()。
Q: 多个async函数如何错误处理? A: asyncio.gather()可以统一处理:
async def safe_gather():
tasks = [fetch_data(i, i) for i in range(3)]
try:
results = await asyncio.gather(*tasks, return_exceptions=True)
for r in results:
if isinstance(r, Exception):
print(f'任务出错: {r}')
except Exception as e:
print(f'捕获异常: {e}')
最简使用原则:
- IO密集型用async/await,CPU密集型用多进程
- 一个async函数内至少有一个await,否则没意义
- 用asyncio.run()启动,用create_task()实现并发
总结:理解事件循环模型是关键。
说错应该叫非阻塞
我疑问就是这里了, 这个非阻塞是通过什么方式实现的?
通过事件循环,3.4 时是 yield
意思是该开线程还得开, 该 yield 还得 yield, 就是换了个词而已 ???
不开线程,异步 io 的意思就是 io 操作不会阻塞当前线程
https://github.com/python/cpython/blob/97cf0828727ac2a269c89c5aa09570a69a22c83c/Lib/asyncio/tasks.py#L593
将事件添加到事件循环里,由事件循环调度。
scrapy 也是用这种方法实现每分钟打印一次日志的。
你可以看看源码, asyncio.sleep 是通过在事件循环里 call_later 来实现的. 在这个 async 函数中阻塞, 但是不会阻塞事件循环.
作用就是模拟异步 io 操作.
所以换成你自己的处理函数的时候, 比如是链接数据库, http 请求这样的可以异步化的, 就用类似的异步化的库(比如 aiohttp, aioredis, motor 等等)来 await query()调用.
如果不能异步化的就开线程用 ThreadPoolExecutor 转成异步调用.
解释线程 /异步 /非阻塞 /event loop 的文章网上一大堆,你看了也不会真的懂。
先熟悉熟悉这个包:
https://docs.python.org/3/library/selectors.html#module-selectors
https://docs.python.org/3/library/select.html#module-select
他们说的再多虚头巴脑都扯淡,核心内容就是 selector。
那你要看很多了,future, loop, epoll 都要看, 是他们相互协作形成的,await==yield from 只是一个方便的写法,也可以用回调来写,只是会麻烦一些

