深入理解 Python 的 async 和 await

在现代软件开发中,异步编程已成为提高应用程序性能的关键手段,尤其是在处理 I/O 密集型任务时。Python 提供了强大的 asyncawait 关键字,使得编写异步代码更加直观和高效。在本文中,我们将深入探讨 Python 中的 asyncawait,结合实例、协程的概念,以及底层原理。


一、协程的概念

1.1 什么是协程?

协程(Coroutine)是一种 协作式的并发计算。与传统的多线程并发不同,协程通过主动让出执行权来实现任务的切换,而不是依赖操作系统调度。

在 Python 中,协程是由 协程函数 定义的,并通过 async def 关键字实现。协程不会立即执行,而是返回一个协程对象,表示任务的执行逻辑和状态。协程对象的执行需要借助 事件循环 来调度。

1.2 协程的优点
  • 轻量级:协程运行在单线程中,不需要线程切换带来的开销。
  • 非阻塞:在等待 I/O 时,其他协程可以继续执行。
  • 适用于 I/O 密集型任务:比如文件操作、网络请求等。

二、asyncawait 的基础用法

2.1 定义协程函数

协程函数是使用 async def 定义的函数,调用它时不会立即执行,而是返回一个协程对象。

import asyncio

# 定义一个协程函数
async def say_hello():
    print("Hello!")
    await asyncio.sleep(1)  # 模拟异步等待
    print("Goodbye!")
2.2 调用和执行协程

协程需要通过 await 或事件循环来执行。直接调用协程函数只会返回一个协程对象。

# 错误示例:直接调用协程函数
coro = say_hello()
print(coro)  # <coroutine object say_hello at 0x...>

# 正确示例:通过 asyncio.run 执行协程
asyncio.run(say_hello())
2.3 await 的作用

await 关键字用于暂停协程的执行,等待一个可等待对象完成,并返回结果。可等待对象包括协程对象、asyncio 提供的异步操作(如 asyncio.sleep)等。

async def delayed_hello():
    print("Waiting for 2 seconds...")
    await asyncio.sleep(2)
    print("Done!")

示例:

import asyncio

# 定义一个协程函数
async def say_hello():
    print("Hello!")
    await asyncio.sleep(1)  # 模拟异步等待
    print("Goodbye!")

async def say_hi():
    print("Waiting for 2 seconds...")
    await asyncio.sleep(2)
    print("Done!")

async def say():
    await say_hello()
    await say_hi()

asyncio.run(say())

在这里插入图片描述

三、协程的原理与事件循环

3.1 协程对象是什么?

当调用一个协程函数时,它不会执行,而是返回一个 协程对象。协程对象保存了协程的执行逻辑和当前状态,只有在事件循环中调度时才会真正运行。

import asyncio

async def example():
    await asyncio.sleep(1)

coro = example()
print(type(coro))  # <class 'coroutine'>

可以使用 asyncio.iscoroutine() 来判断某个对象是否为协程对象。

3.2 事件循环

事件循环是 asyncio 模块的核心,用于管理和调度协程的执行。asyncio.run() 是高层的事件循环管理器,它会:

  1. 创建一个新的事件循环。
  2. 执行传入的协程,调度所有任务。
  3. 关闭事件循环,清理资源。

示例:

async def task1():
    print("Task 1 started")
    await asyncio.sleep(1)
    print("Task 1 completed")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(2)
    print("Task 2 completed")

async def main():
    await asyncio.gather(task1(), task2())  # 并发执行多个任务

# 启动事件循环
asyncio.run(main())

输出:

Task 1 started
Task 2 started
Task 1 completed
Task 2 completed

在这里插入图片描述

3.3 asyncio.run() 的底层原理

asyncio.run() 的核心逻辑如下:

  1. 检查当前线程是否已有运行的事件循环,防止嵌套。
  2. 创建新的事件循环并将协程添加到其中。
  3. 使用 loop.run_until_complete() 执行协程,阻塞主线程直到完成。
  4. 关闭事件循环,释放资源。

代码示意:

def asyncio_run(coro):
    loop = asyncio.new_event_loop()  # 创建新的事件循环
    asyncio.set_event_loop(loop)
    try:
        return loop.run_until_complete(coro)  # 执行协程
    finally:
        loop.close()  # 关闭事件循环

在这里插入图片描述


四、实例:并发处理多个任务

假设我们需要同时处理多个异步任务,例如向多个 API 发送请求。可以使用 asyncio.gather 来并发执行多个协程。

import asyncio

async def fetch_data(api):
    print(f"Fetching data from {api}...")
    await asyncio.sleep(2)  # 模拟网络延迟
    print(f"Data from {api} received!")

async def main():
    apis = ["API_1", "API_2", "API_3"]
    # 并发执行多个协程
    await asyncio.gather(*(fetch_data(api) for api in apis))

asyncio.run(main())

输出:

Fetching data from API_1...
Fetching data from API_2...
Fetching data from API_3...
Data from API_1 received!
Data from API_2 received!
Data from API_3 received!

五、注意事项与最佳实践

5.1 注意事项
  1. 不能嵌套调用 asyncio.run()asyncio.run() 会创建一个新的事件循环,嵌套调用会引发 RuntimeError
  2. 协程不能直接调用:协程必须通过 await 或事件循环执行,直接调用只会返回协程对象。
  3. 事件循环的生命周期:确保事件循环在程序退出前正确关闭,防止资源泄漏。
5.2 最佳实践
  • 使用 asyncio.run() 作为程序的入口,统一管理事件循环。
  • 使用 asyncio.gather()asyncio.create_task() 并发执行多个任务。
  • 在协程中处理异常,避免未捕获的错误导致程序崩溃。

六、总结

Python 的 asyncawait 提供了一种优雅的方式来实现异步编程。通过协程和事件循环,开发者可以高效地处理 I/O 密集型任务,而无需引入复杂的线程或进程管理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值