Python中关于async/await的用法与常见疑问

对于协程, 一些使用装饰器或者队列的实例基本都能理解, 但是这个 async/await 看了很多实例也不太懂该怎么理解. 网上 90%的教程的例子大概基本都是这样的:

async def func():
    print("start...")
    r = await asyncio.sleep(5)
    print("end...")

这个 asyncio.sleep()从一开始就定义是非阻塞的, 问题是它到底是通过什么方式实现非阻塞的呢? 换成我自己的处理函数, 这个函数应该用什么方式协程非阻塞的?


Python中关于async/await的用法与常见疑问

11 回复

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())

关键点解析:

  1. async def:声明一个协程函数,调用它不会立即执行,而是返回一个协程对象
  2. await:只能用在async函数内部,表示“挂起当前协程,等待后面的awaitable对象完成”
  3. 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 只是一个方便的写法,也可以用回调来写,只是会麻烦一些

回到顶部