概念
按照 Python 文档的定义:
asyncio 是用来编写并发代码的库,使用
async/await语法。
它适合网络请求、流式连接、子进程等需要等待 I/O 的任务。asyncio 提供异步文件描述符接口,但普通磁盘文件读写仍可能阻塞事件循环,通常要交给线程或专用异步库。
理解 asyncio 需要区分协程、Task 和事件循环:协程可以被 await 直接驱动,也可以被包装成 Task 后由事件循环并发调度。Task 不是协程执行的前置条件,而是并发调度协程的一种方式。
Event Loop(事件循环)
Event loop 是 asyncio 的调度中心,负责运行就绪的 Task、处理 I/O 事件,并在等待条件满足后恢复任务。
asyncio.run(main()) 会创建事件循环,把顶层协程运行至完成,然后关闭事件循环。
Coroutine(协程)
协程函数(coroutine function)
使用
async def定义协程函数。比如编写一个普通函数
print_hello()def print_hello(): print("Hello")调用
print_hello()函数输出为Hello当把
def改成async def时async def print_hello(): print("Hello")此时
print_hello是协程函数。协程对象(coroutine object)
调用协程函数会返回协程对象,但不会立即执行函数体。
async def print_hello(): print("Hello") print(print_hello())输出结果为
<coroutine object print_hello at 0x7fdb21345b40>直接调用协程函数不会执行里面的代码(输出 Hello),只会返回一个协程对象。
Task(任务)
Task 是对协程的调度包装。asyncio.create_task(coro()) 会把协程安排到当前事件循环中运行,并立即返回 Task 对象;当前协程可以继续做其他工作,再在需要结果时 await task。
基本用法
await
最小的顺序执行例子:
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
asyncio.run(main())
把两个协程依次 await:
import asyncio
from datetime import datetime
async def print_hello():
await asyncio.sleep(1)
print('Hello ...')
async def print_world():
await asyncio.sleep(1)
print('... World!')
async def main():
print(datetime.now().strftime("%H:%M:%S"))
await print_hello()
await print_world()
print(datetime.now().strftime("%H:%M:%S"))
asyncio.run(main())
执行后输出为
22:13:22
Hello …
… World!
22:13:24
总耗时约 2 秒,因为 print_world() 要等 print_hello() 完成后才开始。这里的 await coroutine 不会自动创建一个独立 Task;它会暂停当前协程,直接驱动被等待的协程,直到获得结果。要让两个操作重叠执行,需要显式创建 Task,或使用 gather() / TaskGroup。
传给 asyncio.run() 的 main() 是协程对象。它是程序的顶层入口,asyncio.run() 负责把它运行至完成。
asyncio.create_task()
asyncio.create_task() 会立即把协程包装成 Task 并交给当前事件循环调度。创建 Task 后,当前协程可以继续执行其他代码,直到显式 await 该 Task 获取结果。
import asyncio
async def worker(name, delay):
await asyncio.sleep(delay)
return f"{name} finished"
async def main():
task = asyncio.create_task(worker("task-a", 1))
print("task created")
result = await task
print(result)
asyncio.run(main())
不要创建 Task 后直接退出事件循环,否则尚未完成的任务会被取消。需要结果或需要确保任务执行完成时,应保存 Task 引用并等待它。
asyncio.gather()
asyncio.gather() 可以同时等待多个可等待对象。这里两个任务会并发执行,总耗时接近较慢任务的 2 秒,而不是两者相加的 3 秒。
import asyncio
async def worker(name, delay):
await asyncio.sleep(delay)
return f"{name} finished"
async def main():
results = await asyncio.gather(
worker("task-a", 2),
worker("task-b", 1),
)
print(results)
asyncio.run(main())
返回结果的顺序与传入顺序一致,而不是按任务完成顺序排列。默认情况下,任务抛出的异常会向调用方传播;需要把异常作为结果收集时,可以使用 return_exceptions=True,但随后必须逐项检查结果。
asyncio.TaskGroup
Python 3.11 及以上可使用 TaskGroup 管理一组相关任务。退出上下文时会等待所有任务;任一任务失败时,其余任务会被取消,因此比手工维护多个 Task 更适合结构化并发。
import asyncio
async def worker(name, delay):
await asyncio.sleep(delay)
return f"{name} finished"
async def main():
async with asyncio.TaskGroup() as group:
task_a = group.create_task(worker("task-a", 2))
task_b = group.create_task(worker("task-b", 1))
print(task_a.result(), task_b.result())
asyncio.run(main())
参考
asyncio的概念概述
asyncio — 异步 I/O
协程与任务
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。