Dev
Python asyncio 核心概念与必用技巧
Python asyncio 核心概念与必用技巧
引言
Python 的 asyncio 是编写并发代码的利器,尤其适合 I/O 密集型任务。很多开发者觉得异步编程难以理解,其实只要掌握几个核心概念,就能写出高效的异步程序。本文总结我在实际开发中每天都会用到的 asyncio 知识。
一、核心概念(必须理解)
1. 事件循环 (Event Loop)
事件循环是 asyncio 的心脏,它负责调度和执行异步任务。
import asyncio
# 获取当前事件循环
loop = asyncio.get_running_loop() # Python 3.7+ 推荐
# 运行一个协程直到完成
async def main():
print("Hello")
asyncio.run(main()) # 自动创建、运行、关闭事件循环💡 实际开发中,你几乎不需要手动操作事件循环,
asyncio.run()搞定一切。
2. 协程 (Coroutine)
用 async def 定义的就是协程,它是异步函数。
# 定义协程
async def fetch_data():
await asyncio.sleep(1)
return {"data": 42}
# 调用协程会返回协程对象,不会立即执行
coro = fetch_data()
print(type(coro)) # <class 'coroutine'>
# 运行协程的三种方式
# 方式1:asyncio.run()(最常用)
result = asyncio.run(fetch_data())
# 方式2:await(在另一个协程中)
async def main():
result = await fetch_data()
# 方式3:create_task(并发执行,见下文)3. await 关键字
await 用于挂起当前协程,让事件循环去执行其他任务。只有可等待对象才能被 await:
- 协程对象
- Future 对象
- Task 对象
async def demo():
# ✅ 正确:await 协程
result = await fetch_data()
# ❌ 错误:await 普通函数
# await time.sleep(1) # TypeError
# ✅ 正确:await asyncio 提供的可等待对象
await asyncio.sleep(1)4. Task 任务
Task 是并发执行的关键。它将协程包装成任务,提交给事件循环调度。
async def task_func(name, delay):
await asyncio.sleep(delay)
print(f"Task {name} done")
return name
async def main():
# 创建任务(立即开始调度,但还未执行)
task1 = asyncio.create_task(task_func("A", 2))
task2 = asyncio.create_task(task_func("B", 1))
# 等待任务完成,同时获取返回值
result1 = await task1
result2 = await task2
print(f"Results: {result1}, {result2}")
# 输出顺序:Task B done, Task A done
# 总耗时约2秒,而非3秒
asyncio.run(main())⚠️ 重要:创建任务后若没有
await,任务可能永远不会执行。
5. Future 对象
Future 是低层级的可等待对象,代表一个尚未完成的操作。Task 是 Future 的子类。日常开发中几乎不会直接使用 Future,但理解它有助于理解 asyncio 原理。
# 很少直接使用,了解即可
future = asyncio.Future()
async def set_result():
await asyncio.sleep(1)
future.set_result("Done")
asyncio.create_task(set_result())
result = await future # 等待结果二、开发必用技巧
1. 并发执行多个协程
import asyncio
async def fetch(url):
await asyncio.sleep(1)
return f"Data from {url}"
# 方式1:gather - 最常用,保持顺序
async def use_gather():
results = await asyncio.gather(
fetch("url1"),
fetch("url2"),
fetch("url3"),
)
print(results) # 顺序与传入一致
# 方式2:wait - 更灵活的并发控制
async def use_wait():
tasks = [fetch(f"url{i}") for i in range(3)]
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
for task in done:
print(task.result())
# 方式3:as_completed - 谁先完成先处理谁
async def use_as_completed():
tasks = [fetch(f"url{i}") for i in range(3)]
for coro in asyncio.as_completed(tasks):
result = await coro
print(result) # 顺序不固定2. 超时控制
async def slow_operation():
await asyncio.sleep(10)
return "Done"
async def with_timeout():
try:
# 等待最多5秒
result = await asyncio.wait_for(slow_operation(), timeout=5)
except asyncio.TimeoutError:
print("操作超时了!")
# 可选:取消任务
# task.cancel()3. 优雅地取消任务
async def cancellable_task():
try:
while True:
await asyncio.sleep(1)
print("Working...")
except asyncio.CancelledError:
print("任务被取消,执行清理工作")
# 清理资源
raise # 重新抛出,让上层处理
async def main():
task = asyncio.create_task(cancellable_task())
await asyncio.sleep(3)
task.cancel() # 发送取消信号
try:
await task
except asyncio.CancelledError:
print("任务已取消")4. 同步原语(解决竞态条件)
# Lock - 互斥锁
async def use_lock():
lock = asyncio.Lock()
async with lock:
# 临界区,同一时间只有一个协程能进入
await asyncio.sleep(1)
# Queue - 生产者-消费者模式(必用!)
async def producer_consumer():
queue = asyncio.Queue(maxsize=10)
async def producer():
for i in range(5):
await queue.put(f"item {i}")
await asyncio.sleep(0.1)
await queue.put(None) # 终止信号
async def consumer():
while True:
item = await queue.get()
if item is None:
break
print(f"Processing {item}")
queue.task_done()
async with asyncio.TaskGroup() as tg: # Python 3.11+
tg.create_task(producer())
tg.create_task(consumer())5. 使用 TaskGroup(Python 3.11+ 推荐)
TaskGroup 自动管理任务生命周期,出现异常时自动取消所有子任务。
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch("url1"))
task2 = tg.create_task(fetch("url2"))
task3 = tg.create_task(fetch("url3"))
# 退出上下文时,自动等待所有任务完成
results = [t.result() for t in [task1, task2, task3]]三、常见陷阱与最佳实践
❌ 错误示例
# 1. 忘记 await(协程不会执行)
async def bad():
fetch_data() # 忘记了 await!
# 2. 在异步代码中使用同步阻塞调用
async def bad():
time.sleep(1) # 应使用 await asyncio.sleep(1)
# 3. 创建任务但不 await 或 store
async def bad():
asyncio.create_task(fetch_data()) # 任务可能被垃圾回收
await asyncio.sleep(0.1) # 依赖运气✅ 正确实践
# 1. 始终 await 或 create_task
async def good():
result = await fetch_data() # 方式1:等待结果
task = asyncio.create_task(fetch_data()) # 方式2:保存任务引用
# 稍后 await task
# 2. 使用 asyncio.to_thread 处理阻塞代码
async def good():
result = await asyncio.to_thread(time.sleep, 1) # 在线程池中执行
# 3. 使用 TaskGroup 自动管理(Python 3.11+)
async def good():
async with asyncio.TaskGroup() as tg:
task = tg.create_task(fetch_data())四、实战模板:异步 Web 请求
import asyncio
import aiohttp
async def fetch_url(session, url):
try:
async with session.get(url, timeout=10) as response:
return await response.text()
except Exception as e:
print(f"Error fetching {url}: {e}")
return None
async def fetch_all(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
return await asyncio.gather(*tasks)
async def main():
urls = [
"https://api.example.com/1",
"https://api.example.com/2",
"https://api.example.com/3",
]
results = await fetch_all(urls)
for url, data in zip(urls, results):
print(f"{url}: {len(data) if data else 'Failed'} bytes")
asyncio.run(main())总结
| 概念 | 使用频率 | 说明 |
|---|---|---|
async def / await | 100% | 定义和调用协程 |
asyncio.run() | 100% | 入口函数 |
asyncio.create_task() | 90% | 创建并发任务 |
asyncio.gather() | 80% | 并发收集结果 |
asyncio.wait_for() | 60% | 超时控制 |
asyncio.Queue() | 40% | 生产者-消费者 |
asyncio.Lock() | 20% | 保护共享资源 |
TaskGroup | 30% | 新项目推荐使用 |
掌握以上概念和技巧,你已经能应对 95% 的 asyncio 开发场景。建议从 gather 和 create_task 开始实践,逐步深入。
📌 一句话总结:
async定义协程,await让出控制权,create_task实现并发,gather收集结果。