async 和 await 详细解析

Python asyncawait 详细解析

asyncawait 是 Python 3.5 及以上版本引入的异步编程核心语法,用于实现「非阻塞式」代码执行,解决传统同步编程中高IO等待(如网络请求、数据库操作、文件读写)导致的资源浪费问题,大幅提升程序在高IO场景下的并发性能。

一、核心概念铺垫:同步 vs 异步

在理解 async/await 之前,先明确同步编程和异步编程的核心差异:

1. 同步编程(默认模式)

  • 执行逻辑:代码按顺序串行执行,一个任务执行完成(包括等待时间)后,下一个任务才会开始。
  • 典型问题:遇到IO等待(如请求接口、读取大文件)时,整个程序会「阻塞」在该任务上,浪费CPU资源。
    示例:同步请求3个接口,每个接口耗时2秒(其中1.9秒是网络等待),总耗时约6秒,CPU在等待期间处于闲置状态。

2. 异步编程(async/await 实现)

  • 执行逻辑:代码按事件循环驱动的非阻塞模式执行,当一个任务遇到IO等待时,不会阻塞整个程序,而是将控制权交还给事件循环,去执行其他就绪任务,待IO任务完成后,再继续执行该任务的后续代码。
  • 核心优势:在IO等待期间充分利用CPU资源,提升程序并发处理能力。
    示例:异步请求3个接口,总耗时约2秒(接近单个接口的耗时),CPU在等待期间可处理其他任务。

二、async:定义异步函数/协程

1. 核心定义

async 是一个关键字,用于定义异步函数(也称为「协程函数」,Coroutine Function),标志着该函数内部包含非阻塞执行的逻辑,不能直接同步调用执行,必须通过异步方式触发。

2. 语法格式

# 定义异步函数(协程函数)
async def 异步函数名(参数列表):
    # 函数体(可包含 await 语句,也可不含)
    执行逻辑
    return 返回值

3. 关键特性

  • 异步函数被调用时,不会立即执行函数体,而是返回一个「协程对象(Coroutine Object)」,这是异步函数与普通同步函数的核心区别。
  • 异步函数的返回值不能通过普通 = 赋值获取,必须通过 await 或异步框架的相关方法获取。

4. 示例:定义异步函数

# 定义一个简单的异步函数
async def async_hello():
    print("进入异步函数")
    # 模拟IO等待(后续会用 asyncio.sleep 替代,这里先演示结构)
    await asyncio.sleep(1)
    print("异步函数执行完成")
    return "异步函数返回结果"

# 直接调用异步函数,不会执行函数体,仅返回协程对象
coro = async_hello()
print("调用异步函数返回的结果:", coro)  # 输出:调用异步函数返回的结果: <coroutine object async_hello at 0x10xxxxx>

5. 协程的本质

协程(Coroutine)是「轻量级线程」,由Python程序自身控制(而非操作系统内核控制),切换开销远小于传统线程。异步函数就是协程的载体,async 关键字的核心作用是标记该函数为协程函数,使其能被事件循环调度执行。

三、await:挂起协程等待异步任务完成

1. 核心定义

await 是一个关键字,只能在异步函数(async def 定义的函数)内部使用,用于「挂起当前协程的执行」,等待一个「可等待对象(Awaitable)」执行完成并返回结果,在此期间,将控制权交还给事件循环,允许其他协程执行。

2. 核心作用

  1. 等待异步任务完成:阻塞当前协程(而非整个程序),直到被等待的对象执行完毕。
  2. 非阻塞式等待:在等待期间,事件循环可以调度其他就绪的协程执行,充分利用CPU资源。
  3. 获取异步任务返回值:await 会接收被等待对象的返回值,可直接赋值给变量使用。

3. 可等待对象(Awaitable)

await 后面只能跟「可等待对象」,否则会抛出 TypeError,常见的可等待对象有3类:

  • 协程对象(Coroutine):async def 函数调用后返回的对象(最常用)。
  • 任务对象(Task):由 asyncio.create_task()loop.create_task() 创建的对象,用于封装协程,支持并发执行多个协程。
  • 未来对象(Future):表示一个尚未完成的异步操作结果,通常用于底层异步编程(如异步IO框架实现)。

4. 语法格式

async def 异步函数名():
    # 等待可等待对象执行完成,并获取返回值
    结果 = await 可等待对象
    后续执行逻辑

5. 示例:await 等待协程执行

import asyncio

# 定义异步函数(模拟IO任务)
async def simulate_io_task(task_name, delay):
    print(f"开始执行异步任务:{task_name},等待 {delay} 秒")
    # await 等待 asyncio.sleep(异步睡眠,非阻塞)
    await asyncio.sleep(delay)  # asyncio.sleep 是异步函数,返回协程对象
    print(f"异步任务:{task_name} 执行完成")
    return f"{task_name} 返回结果"

# 定义主异步函数
async def main():
    print("===== 开始执行主异步函数 =====")
    # 依次等待3个异步任务执行(串行异步,总耗时约 1+2+3=6 秒)
    result1 = await simulate_io_task("任务1", 1)
    result2 = await simulate_io_task("任务2", 2)
    result3 = await simulate_io_task("任务3", 3)
    
    # 打印返回结果
    print("\n===== 所有任务执行完成 =====")
    print("结果1:", result1)
    print("结果2:", result2)
    print("结果3:", result3)

# 启动事件循环,执行主协程
if __name__ == "__main__":
    asyncio.run(main())

6. 关键注意点

  • await 不能在同步函数中使用:如果在普通 def 定义的同步函数中使用 await,会直接抛出语法错误。
  • await 会挂起当前协程,而非整个程序:只有当前协程被阻塞,事件循环可以正常调度其他协程。
  • 多个 await 串行执行时,仍会按顺序等待:如上述示例,3个任务串行执行,总耗时等于各任务耗时之和,若要实现并发,需使用 asyncio.create_task() 封装任务。

四、异步编程的核心:事件循环(Event Loop)

asyncawait 本身无法直接运行异步代码,必须依赖「事件循环」(Event Loop)—— 它是Python异步编程的「核心调度器」,负责管理所有协程的执行顺序、分发IO事件、切换协程等。

1. 事件循环的核心职责

  1. 注册和调度协程/任务。
  2. 监控协程的执行状态,当协程被 await 挂起时,切换到其他就绪协程。
  3. 当被挂起的协程完成IO等待后,将其重新加入就绪队列,等待继续执行。
  4. 终止整个异步程序的执行(所有任务完成后)。

2. 常用事件循环操作

在Python 3.7及以上版本,推荐使用 asyncio.run() 来启动事件循环,它会自动创建、运行和关闭事件循环,简化异步代码的启动流程。

示例:并发执行多个异步任务(使用 asyncio.create_task()
import asyncio

async def simulate_io_task(task_name, delay):
    print(f"开始执行异步任务:{task_name},等待 {delay} 秒")
    await asyncio.sleep(delay)
    print(f"异步任务:{task_name} 执行完成")
    return f"{task_name} 返回结果"

async def main():
    print("===== 开始执行主异步函数(并发模式) =====")
    # 1. 创建任务对象(将协程封装为Task,加入事件循环就绪队列)
    task1 = asyncio.create_task(simulate_io_task("任务1", 1))
    task2 = asyncio.create_task(simulate_io_task("任务2", 2))
    task3 = asyncio.create_task(simulate_io_task("任务3", 3))
    
    # 2. 并发等待所有任务完成(总耗时约 3 秒,等于最长任务的耗时)
    result1 = await task1
    result2 = await task2
    result3 = await task3
    
    # 3. 打印返回结果
    print("\n===== 所有任务执行完成 =====")
    print("结果1:", result1)
    print("结果2:", result2)
    print("结果3:", result3)

if __name__ == "__main__":
    # asyncio.run() 自动创建事件循环,运行主协程,执行完毕后关闭事件循环
    asyncio.run(main())
执行结果分析
===== 开始执行主异步函数(并发模式) =====
开始执行异步任务:任务1,等待 1 秒
开始执行异步任务:任务2,等待 2 秒
开始执行异步任务:任务3,等待 3 秒
异步任务:任务1 执行完成
异步任务:任务2 执行完成
异步任务:任务3 执行完成

===== 所有任务执行完成 =====
结果1: 任务1 返回结果
结果2: 任务2 返回结果
结果3: 任务3 返回结果
  • 3个任务几乎同时启动,总耗时约3秒(等于耗时最长的任务3),实现了真正的并发异步执行。
  • 这就是 async/await 的核心价值:在IO密集型场景下,通过非阻塞等待大幅提升并发效率。

五、async/await 的使用场景与限制

1. 最佳使用场景(IO密集型任务)

async/await 对IO密集型任务有显著性能提升,常见场景包括:

  • 网络请求:接口调用、爬虫、微服务通信(推荐使用 aiohttp 替代 requests)。
  • 数据库操作:异步数据库查询(推荐使用 asyncmy 替代 pymysqlasyncpg 替代 psycopg2)。
  • 文件读写:大文件异步读写(推荐使用 aiofiles 替代内置 open)。
  • 消息队列:异步消费/生产消息。

2. 不适用场景(CPU密集型任务)

async/await 对CPU密集型任务(如大数据计算、加密解密、图形渲染)几乎没有性能提升,甚至可能因为协程切换开销导致性能下降。

  • 原因:CPU密集型任务会持续占用CPU资源,不会产生IO等待,事件循环无法切换协程,无法发挥异步编程的优势。
  • 替代方案:对于CPU密集型任务,推荐使用 multiprocessing(多进程)或 concurrent.futures.ProcessPoolExecutor

3. 关键使用限制

  1. await 只能在 async def 函数内部使用,不能在同步函数(def)或全局作用域中使用。
  2. 异步函数不能直接同步调用,必须通过 asyncio.run()awaitasyncio.create_task() 触发执行。
  3. 异步代码必须使用异步库:同步库(如 requestspymysql)会阻塞事件循环,导致异步编程失效,必须使用对应的异步库(如 aiohttpasyncmy)。
  4. 协程切换是「协作式」的,而非「抢占式」的:只有当协程遇到 await 挂起时,才会将控制权交还给事件循环,若一个协程长时间不挂起,会阻塞其他协程的执行。

六、async/await 与同步代码的对比示例

1. 同步代码(requests 调用接口)

import requests
import time

def sync_request(url, delay):
    print(f"开始同步请求:{url}")
    # 同步请求(阻塞等待)
    response = requests.get(url)
    time.sleep(delay)  # 模拟额外等待
    print(f"同步请求:{url} 完成,状态码:{response.status_code}")
    return response.status_code

def main():
    start_time = time.perf_counter()
    # 串行执行3个同步请求
    sync_request("https://httpbin.org/get", 1)
    sync_request("https://httpbin.org/get", 1)
    sync_request("https://httpbin.org/get", 1)
    end_time = time.perf_counter()
    print(f"\n总耗时:{end_time - start_time:.2f} 秒")

if __name__ == "__main__":
    main()
执行结果

总耗时约 6~9 秒(包含网络请求时间+3秒睡眠时间),全程串行阻塞。

2. 异步代码(aiohttp 调用接口)

import aiohttp
import asyncio
import time

async def async_request(url, delay):
    print(f"开始异步请求:{url}")
    # 异步请求(非阻塞等待)
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            await asyncio.sleep(delay)  # 异步睡眠(非阻塞)
            print(f"异步请求:{url} 完成,状态码:{response.status}")
            return response.status

async def main():
    start_time = time.perf_counter()
    # 并发执行3个异步请求
    task1 = asyncio.create_task(async_request("https://httpbin.org/get", 1))
    task2 = asyncio.create_task(async_request("https://httpbin.org/get", 1))
    task3 = asyncio.create_task(async_request("https://httpbin.org/get", 1))
    await task1
    await task2
    await task3
    end_time = time.perf_counter()
    print(f"\n总耗时:{end_time - start_time:.2f} 秒")

if __name__ == "__main__":
    asyncio.run(main())
执行结果

总耗时约 2~3 秒(包含网络请求时间+1秒睡眠时间),全程并发非阻塞,性能提升显著。

七、总结

  1. async:用于定义协程函数(异步函数),标记函数包含非阻塞逻辑,调用后返回协程对象,不立即执行。
  2. await:仅在协程函数内部使用,用于挂起当前协程,等待可等待对象完成,期间将控制权交还给事件循环,实现非阻塞等待。
  3. 核心价值:在IO密集型场景下,通过事件循环调度协程,充分利用CPU资源,大幅提升程序并发处理能力。
  4. 运行依赖:异步代码必须通过 asyncio.run() 启动事件循环,且需配合异步库使用,避免同步库阻塞事件循环。
  5. 适用边界:仅适用于IO密集型任务,CPU密集型任务推荐使用多进程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值