Python await 详解:从入门到精通

180次阅读
没有评论

共计 3217 个字符,预计需要花费 9 分钟才能阅读完成。

在现代 Python 开发中,异步编程(async programming)是提升程序并发能力的重要方式,而 await 关键字则是其中的核心概念。本文将详细解析 await 的作用、工作原理、适用场景,并通过代码示例让你彻底掌握 await 的使用。


1. await 是什么?

await 是 Python 异步编程 (asynchronous programming)中的关键字,它用于 等待一个可等待对象(awaitable object)执行完毕 。它只能在 async def 定义的 异步函数 内部使用。

在传统的同步代码中,调用一个函数会阻塞当前线程,直到函数返回结果。但在异步代码中,使用 await 可以让程序在等待的同时执行其他任务,提高并发能力。


2. await 的基本用法

在 Python 中,await 只能用于 可等待对象,包括:

  1. 协程(coroutine)
  2. asyncio.Future 对象
  3. asyncio.Task 对象

来看一个简单的示例:

import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(2)  # 模拟耗时操作
    print("World")

async def main():
    await say_hello()  # 等待协程执行完毕

asyncio.run(main())  # 运行主协程

代码解析

  1. async def say_hello() 定义了一个异步函数。
  2. await asyncio.sleep(2) 让程序暂停 2 秒,但不会阻塞整个事件循环。
  3. await say_hello() 确保 say_hello() 执行完毕后才继续执行后续代码。
  4. asyncio.run(main()) 启动事件循环,运行 main()

输出:

Hello(2 秒后)World

3. awaitasync 的关系

async 用于定义异步函数,而 await 则用于调用异步函数或可等待对象。

错误示例:

import asyncio

async def wrong():
    asyncio.sleep(2)  # ❌ 错误:没有用 `await`
    print("Done")

asyncio.run(wrong())

这会导致 RuntimeWarning,因为 asyncio.sleep(2) 返回的是一个 协程对象,而不是直接执行它。正确的做法是:

await asyncio.sleep(2)

4. await 的底层工作原理

await 本质上是一个 协程调度机制

  • 当遇到 await 时,当前协程会 暂停执行,将控制权交给事件循环(event loop)。
  • 事件循环可以在等待的同时执行其他任务。
  • await 的任务完成后,协程恢复执行,继续往下运行。

示例:

import asyncio

async def task(name, delay):
    print(f"Task {name} started")
    await asyncio.sleep(delay)
    print(f"Task {name} finished")

async def main():
    await task("A", 2)
    await task("B", 1)

asyncio.run(main())

输出:

Task A started(等待 2 秒)Task A finished
Task B started(等待 1 秒)Task B finished

缺点 :虽然是异步函数,但 await 让任务 A 必须等任务 B 完成后 才开始执行,依然是 串行执行


5. await 并发执行多个任务

如果要让多个任务 同时运行,可以使用 asyncio.gather()asyncio.create_task()

方法 1:使用 asyncio.gather()

import asyncio

async def task(name, delay):
    print(f"Task {name} started")
    await asyncio.sleep(delay)
    print(f"Task {name} finished")

async def main():
    await asyncio.gather(task("A", 2),
        task("B", 1)
    )

asyncio.run(main())

输出:

Task A started
Task B started(等待 1 秒)Task B finished(等待 1 秒)Task A finished

解释

  • asyncio.gather() 并发运行多个任务,而不会像 await 那样串行执行。

方法 2:使用 asyncio.create_task()

import asyncio

async def task(name, delay):
    print(f"Task {name} started")
    await asyncio.sleep(delay)
    print(f"Task {name} finished")

async def main():
    task1 = asyncio.create_task(task("A", 2))
    task2 = asyncio.create_task(task("B", 1))

    await task1
    await task2

asyncio.run(main())

这与 asyncio.gather() 类似,但 asyncio.create_task() 适用于 更灵活的场景,比如在某个地方创建任务后继续执行其他逻辑,而不是等待所有任务完成。


6. await 的常见错误

❌ 1. 在非异步函数中使用 await

import asyncio

async def say_hello():
    await asyncio.sleep(1)
    print("Hello")

def main():
    await say_hello()  # ❌ 语法错误

错误信息

SyntaxError: 'await' outside function

解决方案await 只能在 async def 内部使用,应该改为:

async def main():
    await say_hello()

asyncio.run(main())

❌ 2. 忘记 await

async def fetch_data():
    return asyncio.sleep(2)

async def main():
    result = fetch_data()  # ❌ 这里没有 `await`
    print(result)

asyncio.run(main())

输出

<coroutine object fetch_data at 0x7f8a8b6f9f80>

原因 fetch_data() 返回的是 协程对象,但并没有执行,应该 await fetch_data()


❌ 3. await 阻塞整个程序

有时 await 会导致主程序被阻塞,例如:

async def main():
    await asyncio.sleep(5)  # 阻塞 5 秒

asyncio.run(main())

这虽然是异步代码,但因为 await 没有并发执行其他任务,程序仍然需要等 5 秒才能继续。

优化

async def task():
    print("Task started")
    await asyncio.sleep(5)
    print("Task finished")

async def main():
    task1 = asyncio.create_task(task())
    print("Main function continues...")
    await task1

asyncio.run(main())

这样,主程序不会完全被阻塞,而是继续执行其他代码。


7. 结论

  • await 用于等待一个 可等待对象 执行完毕。
  • async def 定义 异步函数await 只能在异步函数中使用。
  • asyncio.gather()asyncio.create_task() 并发执行多个任务
  • await 不会阻塞整个程序,但可能会导致任务串行执行,需要注意。

推荐学习路径

  1. 先理解 asyncawait 的基本概念。
  2. 练习使用 await asyncio.sleep() 等异步函数。
  3. 结合 asyncio.gather()asyncio.create_task() 实现并发任务。

你学会了吗?欢迎留言交流!🚀

正文完
 0
评论(没有评论)