共计 3217 个字符,预计需要花费 9 分钟才能阅读完成。
在现代 Python 开发中,异步编程(async programming)是提升程序并发能力的重要方式,而 await 关键字则是其中的核心概念。本文将详细解析 await 的作用、工作原理、适用场景,并通过代码示例让你彻底掌握 await 的使用。
1. await 是什么?
await 是 Python 异步编程 (asynchronous programming)中的关键字,它用于 等待一个可等待对象(awaitable object)执行完毕 。它只能在 async def 定义的 异步函数 内部使用。
在传统的同步代码中,调用一个函数会阻塞当前线程,直到函数返回结果。但在异步代码中,使用 await 可以让程序在等待的同时执行其他任务,提高并发能力。
2. await 的基本用法
在 Python 中,await 只能用于 可等待对象,包括:
- 协程(coroutine)
asyncio.Future对象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()) # 运行主协程
代码解析
async def say_hello()定义了一个异步函数。await asyncio.sleep(2)让程序暂停 2 秒,但不会阻塞整个事件循环。await say_hello()确保say_hello()执行完毕后才继续执行后续代码。asyncio.run(main())启动事件循环,运行main()。
输出:
Hello(2 秒后)World
3. await 与 async 的关系
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不会阻塞整个程序,但可能会导致任务串行执行,需要注意。
推荐学习路径:
- 先理解
async和await的基本概念。 - 练习使用
await asyncio.sleep()等异步函数。 - 结合
asyncio.gather()和asyncio.create_task()实现并发任务。
你学会了吗?欢迎留言交流!🚀