一、asyncio(异步IO)
asyncio是Python3.4+引入的异步I/O框架,核心目标是在单线程中实现并发执行,避免传统多线程/多进程的上下文切换开销,适用于I/O密集型场景。如网络请求、文件读写、数据库操作。
python
# 异步IO的本质:
同步编程: 代码按顺序执行,遇到I/O操作会阻塞,直到操作完成
异步编程: 遇到I/O操作时会让出控制权,继续执行其他任务,待I/O操作完成后再回调处理结果
* asyncio的核心是"事件循环+协程",基于"单线程+非阻塞I/O+事件驱动"实现并发:
1. 非阻塞I/O: 操作系统层面支持,I/O操作发起后立即返回,不等待结果
2. 事件驱动: 通过事件循环监听I/O完成事件,触发对应回调
3. 协程: 用户态的轻量级"任务",由开发者控制暂停/恢复,无内核态上下文切换开销
# 核心原理
1. 协程(Coroutine): 异步的基础
协程本身是一种"可暂停、可恢复的函数",由async def定义,调用后返回协程对象,而非直接执行
只有将协程对象交给事件循环调度,才会真正执行
await是协程的"暂停开关": 遇到await时,协程让出控制权给事件循环,直到被await的对象完成,协程才恢复执行
协程仅在await关键字处暂停/切换,其余时间独占线程
2. 事件循环(Event Loop): asyncio的"心脏"
事件循环是asyncio的核心调度器,相当于一个"无限循环",负责:
(1)初始化任务队列(存放待执行的协程/Task)
(2)从队列中取出可执行任务,执行到await处(遇到I/O或暂停)
(3)暂停当前任务,将其放回队列,标记为"等待事件完成"
(4)继续执行下一个可执行任务
(5)当某个任务等待的事件完成(如I/O回调),将其标记为"可执行"
重复上述步骤,直到所有任务完成或主动停止
事件循环负责:
(1)管理所有协程/任务的生命周期(创建、暂停、恢复、销毁)
(2)监听I/O事件(如网络连接、文件读写)、定时器、信号等
(3)当某个任务因I/O阻塞(await)时,切换到其他就绪任务执行
(4)当阻塞的任务就绪,重新调度其执行
事件循环的工作流程:
初始化事件循环 → 加入待执行任务 → 事件循环启动 → 循环:
从任务队列取出就绪任务执行 → 遇到await则暂停任务 → 监听该await对应的I/O事件 →
处理I/O事件,唤醒满足条件的任务 → 事件就绪后恢复协程 → 重复直到所有任务完成
3. 非阻塞I/O与回调
asyncio基于操作系统的异步I/O接口(如epoll、kqueue、IOCP)实现非阻塞操作:
(1)当协程执行到await某个I/O操作时,会将该操作注册到事件循环,然后暂停协程
(2)操作系统监测到I/O就绪后,事件循环会唤醒对应的协程继续执行
(3)全程无线程阻塞,单线程可同时处理数千个I/O任务
4. 任务的封装(Future/Task): 协程的调度与结果管理
"协程本身无法被事件循环直接调度,需封装为Task/Future":
- Future:
(1)代表一个尚未完成的异步操作的结果,是底层抽象,记录异步操作的状态(未完成/完成/异常/取消)
- Task:
(1)Future的子类,专门用于包装协程,使其可被事件循环调度,还能跟踪状态(运行中/完成/取消/异常)
(2)事件循环通过Task管理协程的调度"优先级"和"执行顺序"

1.1、协程函数核心语法
1.1.1、async def:定义协程函数
用async def定义协程函数。与普通函数不同,协程函数在调用时不会立即执行,而是仅返回一个协程对象。
普通函数调用后直接执行,协程函数需通过事件循环调度或者await触发才能执行
python
# 语法格式:
* async def func(*args, **kwargs):
函数体(可包含await/async with/async for)
[return 返回值]
async def task():
print("请求开始")
await asyncio.sleep(2)
print("请求结束,继续执行")
1.1.2、await:暂停/切换协程
await 用于暂停 / 挂起 当前协程的执行,将控制权交还给事件循环 ,等待右侧被 await 的可等待对象完成,恢复当前协程执行并获取其结果 。期间事件循环可调度其他协程。
语法上要求:
1、await 必须在async def 函数内部使用
2、协程内部可await其他协程,实现嵌套
3、await后面必须跟随一个可等待对象(Awaitable) ,如:
(1)协程对象 :async def 函数返回的对象
(2)Task对象 :create_task 创建的异步任务
(3)Future对象 :底层异步结果容器
(4)其他自定义实现了__await__()方法的对象
python
# 定义协程函数
async def my_coroutine(name):
print(f"协程 {name} 开始执行")
await asyncio.sleep(1)
print(f"协程 {name} 执行结束")
return f"{name} 执行结果"
async def main_manual():
# 1. 先创建协程对象(此时协程并未执行)
coro1 = my_coroutine("Coroutine-1")
coro2 = my_coroutine("Coroutine-2")
# 2. 使用await手动启动协程(串行执行,先执行coro1,再执行coro2)
# 会暂停main()这个当前协程的执行,直到coro1运行结束,再继续往下执行
result1 = await coro1
result2 = await coro2
print(f"获取协程结果:{result1}")
if __name__ == "__main__":
asyncio.run(main_manual())
1.1.3、async with:异步上下文管理器
async with 用于异步上下文管理器。它的作用与 with 语句类似,但适用于异步环境,确保在异步操作前后执行特定的设置和清理操作,核心是实现 aenter () 和 aexit() 两个异步方法
python
# 语法格式:
async with 异步上下文对象 as 变量名:
# 上下文内的异步代码
await ...
# 异步上下文管理器针对异步资源操作(如异步文件打开、异步数据库连接),核心是实现 __aenter__() 和 __aexit__() 两个异步方法:
* __aenter__(): 异步进入上下文,返回上下文对象;
* __aexit__(exc_type, exc_val, exc_tb): 异步退出上下文,处理异常、释放资源(如关闭连接/文件)
async with 会自动:
* 调用__aenter__()并await其完成
* 执行上下文内的代码
* 调用__aexit__()并await其完成,无论是否有异常
# 关键特性:
* 只能在async def函数内使用
* 异步上下文对象必须实现 __aenter__和 __aexit__ 方法
* 自动处理资源的"获取-释放",如异步文件关闭、异步网络连接断开
1.1.3.1、自定义异步上下文管理器
python
class AsyncContextManager:
async def __aenter__(self):
print("Enter context")
return self
async def __aexit__(self, exc_type, exc, tb):
print("Exit context")
async def async_with_example():
async with AsyncContextManager() as manager:
print("Inside context")
asyncio.run(async_with_example())
python
class AsyncDatabaseConnection:
def __init__(self, db_name: str):
self.db_name = db_name
self.connection = None
# 异步获取资源(连接数据库)
async def __aenter__(self):
print(f"[异步] 连接数据库 {self.db_name}")
await asyncio.sleep(0.5) # 模拟异步连接耗时
self.connection = f"{self.db_name}_conn"
return self.connection
# 异步释放资源(关闭数据库)
async def __aexit__(self, exc_type, exc_val, exc_tb):
print(f"[异步] 关闭数据库 {self.db_name}")
await asyncio.sleep(0.5) # 模拟异步关闭耗时
self.connection = None
# 处理异常:若代码块出错,打印异常信息
if exc_type:
print(f"数据库操作异常:{exc_val}")
# 返回 True 则不向上传播异常,False 则传播
return False
async def use_db():
async with AsyncDatabaseConnection("test_db") as conn:
print(f"使用数据库连接:{conn}")
# 模拟数据库操作
await asyncio.sleep(0.3)
# 测试异常传播(取消注释)
# raise ValueError("数据插入失败")
asyncio.run(use_db())
python
class AsyncResource:
# 模拟异步连接
async def connect(self):
await asyncio.sleep(0.5)
print("Connection established")
return self
# 模拟异步断开
async def disconnect(self):
await asyncio.sleep(0.5)
print("Connection closed")
# 异步进入上下文
async def __aenter__(self):
print("异步获取资源")
await self.connect()
await asyncio.sleep(0.5) # 模拟异步资源初始化(如连接数据库)
# return "资源对象" # 赋值给as后的变量
# return await self.connect()
return self
# 异步退出上下文
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("异步释放资源")
await self.disconnect()
await asyncio.sleep(0.5) # 模拟异步资源清理(如关闭连接)
# 处理异常:返回 True 则吞掉异常,返回 False 则抛出
if exc_type:
print(f"捕获异常:{exc_type}, {exc_val}")
return False # 不吞异常,向外抛出
async def main():
try:
async with AsyncResource() as res:
print(f"使用资源:{res}")
# 模拟上下文内抛出异常
raise ValueError("上下文内出错")
except ValueError as e:
print(f"外层捕获异常:{e}")
asyncio.run(main())
1.1.3.2、异步文件操作
python
# aiofiles.open()返回异步文件对象,实现了__aenter__/__aexit__
async def async_read_file(file_path):
# 异步打开文件,自动关闭
async with aiofiles.open(file_path, mode='r', encoding='utf-8') as f:
content = await f.read() # 异步读取文件(非阻塞)
print(f"文件内容:{content}")
asyncio.run(async_read_file("demo1.txt"))
1.1.3.3、aiohttp 异步客户端
python
import aiohttp
async def fetch_one(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
urls = ["url1", "url2", "url3"]
tasks = []
# async with 管理异步HTTP会话(自动关闭连接)
async with aiohttp.ClientSession() as session:
for index, url in enumerate(urls):
# 嵌套async with管理单个请求(自动释放连接)
task = asyncio.create_task(fetch_one(session, url), name=f"task-{index + 1}")
tasks.append(task)
# 上面for 循环可以写成
tasks = [asyncio.create_task(fetch_one(session, url), name=f"task-{index + 1}") for index, url in enumerate(urls)]
try:
res_list = await asyncio.gather(*tasks, return_exceptions=True)
# 遍历结果,区分成功和失败的任务
for idx, res in enumerate(res_list):
task_name = f"task-{idx + 1}"
if isinstance(res, Exception):
print(f"{task_name} 执行失败: {res}")
else:
print(f"{task_name} 执行成功: {res}")
return res
# 取消所有未完成的任务
except asyncio.CancelledError:
for task in tasks:
if not task.done():
task.cancel()
print(f"任务{task.get_name()} 已取消")
1.1.4、async for:异步迭代器
async for 用于异步迭代可等待对象的异步迭代器。它的工作方式类似于普通的 for 循环,但可以在异步环境中使用
python
# 语法格式:
async for 元素 in 异步可迭代对象:
# 处理元素的异步代码
await ...
# 异步迭代器针对异步数据流(如异步读取大文件、异步消费消息队列、异步分页查询数据库),核心是实现:
* __aiter__(): 返回异步迭代器对象(自身)
* __anext__(): 异步获取下一个元素,抛出 StopAsyncIteration 表示迭代结束
async for 会自动:
* 调用 __aiter__() 获取迭代器
* 循环调用 __anext__() 并await其结果
* 直到捕获StopAsyncIteration终止迭代
# 关键特性:
* 只能在async def函数内使用;
* 异步可迭代对象必须实现 __aiter__,且返回的迭代器必须实现 __anext__
* 适用于"边异步获取边处理"的场景,如异步读取大文件的行、异步消费Kafka消息
1.1.4.1、自定义异步迭代器
__aiter__ 必须是普通 def 方法,才能返回异步迭代器。使用 async def返回的是一个协程,运行会报错:'async for' received an object from __aiter__ that does not implement __anext__: coroutine
__anext__ :是 async def 方法
python
class AsyncIterator:
def __init__(self):
self.count = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.count < 5:
self.count += 1
return self.count
else:
raise StopAsyncIteration
async def async_for_example():
async for number in AsyncIterator():
print(number)
asyncio.run(async_for_example())
python
class AsyncRange:
def __init__(self, stop):
self.stop = stop
self.current = 0
# 返回异步迭代器自身
def __aiter__(self):
return self
# 异步获取下一个元素
async def __anext__(self):
if self.current >= self.stop:
# 终止迭代,必须抛StopAsyncIteration,而非StopIteration
raise StopAsyncIteration
await asyncio.sleep(0.5)
self.current += 1
return self.current - 1
async def main():
# 异步迭代 0-2
async for num in AsyncRange(3):
print(f"迭代到:{num}")
asyncio.run(main())
1.1.4.2、异步读取大文件
python
import aiofiles
async def async_read_lines(file_path):
async with aiofiles.open(file_path, mode='r', encoding='utf-8') as f:
# 异步迭代文件的每一行(非阻塞)
async for line in f:
print(f"读取行:{line.strip()}")
await asyncio.sleep(0.1)
asyncio.run(async_read_lines("large_file.txt"))
1.1.4.3、aiohttp异步读取流数据
python
import aiohttp
async def read_stream(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
# async for 逐块读取响应流(适合大文件下载)
async for chunk in response.content.iter_chunked(1024):
print(f"Received chunk: {len(chunk)} bytes")
await asyncio.sleep(0.1) # 模拟异步处理
asyncio.run(read_stream("https://www.example.com"))
1.1.4.4、异步生成器
异步生成器用async def + yield简化异步迭代器的实现,无需手动定义 __aiter__和__anext__
python
async def async_generator(num):
for i in range(num):
await asyncio.sleep(0.5)
yield i
async def main():
# 异步迭代生成器的元素
async for val in async_generator(3):
print(f"生成器返回:{val}")
asyncio.run(main())
1.1.5、asyncio.sleep:非阻塞延迟,异步休眠
让当前协程休眠delay秒(非阻塞),休眠期间事件循环可调度其他协程执行;休眠结束后返回result,与 time.sleep() 相反,time.sleep 会阻塞整个线程
python
# 语法参数:
* asyncio.sleep(delay, result=None):
- delay: 延迟时间(秒,浮点数 / 整数)
- result: 延迟结束后返回的结果(可选)
async def main():
# 延迟 1 秒后返回 "sleep done"
result = await asyncio.sleep(1, result="sleep done")
print(result) # 输出:sleep done
asyncio.run(main())
1.1.6、asyncio.shield:保护任务不被外部取消
保护 一个 Awaitable 对象不被外部取消,即使外层调用 cancel() 或 wait_for() 超时,被 shield 保护的任务仍会继续执行
python
# 语法格式:
* asyncio.shield(aw, loop=None)
# 场景:
1. 保护关键任务不被外部取消,如数据持久化任务、事务提交任务、重要的计算任务等
python
async def critical_task():
print("Critical task: Start (this task must complete)")
await asyncio.sleep(5)
print("Critical task: End (successfully completed)")
return "Critical result"
# 场景一:保护任务不被手动取消
async def main():
protected = asyncio.shield(critical_task())
task = asyncio.create_task(protected)
task.cancel() # 尝试取消
try:
await task
except asyncio.CancelledError:
print("外层任务被取消,但保护任务仍在执行")
# 等待保护任务完成
result = await protected
print(result) # 输出:保护任务完成
# 场景二:保护任务不被超时取消
async def main():
try:
# 用 wait_for 设置超时(2 秒),但用 shield 保护 critical_task
protected = asyncio.shield(critical_task())
result = await asyncio.wait_for(protected, timeout=2)
print(f"Main: Task result: {result}")
except TimeoutError:
print("Main: Task timed out after 2 seconds, but critical task continues...")
# 等待一段时间,让 critical_task 完成
await asyncio.sleep(4)
asyncio.run(main())
1.1.7、asyncio.current_task:获取当前运行的Task
返回当前正在运行的 Task 对象(若存在),否则返回None
python
current_task = asyncio.current_task(loop=None)
python
async def task():
current_task = asyncio.current_task()
print(f"Current task name: {current_task.get_name()}")
async def main():
await asyncio.create_task(task(), name="My-Task")
asyncio.run(main())
1.1.8、asyncio.all_tasks:获取事件循环中所有未完成的Task
获取指定事件循环中所有未完成的 Task 对象列表
python
async def dummy_task():
await asyncio.sleep(10)
async def main():
# 创建3个任务
for i in range(3):
asyncio.create_task(dummy_task(), name=f"任务{i+1}")
# 获取所有任务
all_tasks = asyncio.all_tasks()
print("当前所有任务:")
for task in all_tasks:
if task != asyncio.current_task():
print(f"- {task.get_name()}")
task.cancel()
asyncio.run(main())
1.1.9、asyncio.iscoroutine ():判断是否是协程对象
1.1.10、asyncio.iscoroutinefunction ():判断是否是协程函数
1.2、协程的创建和运行
日常开发中:接口请求、数据爬取、简单异步任务:优先使用asyncio.run() ,简洁高效
复杂场景中:异步服务器、自定义循环行为、多线程异步调度:使用手动管理事件循环,更加灵活
python
# asyncio协程运行启动的核心本质:
协程本身是"被动执行"的,必须由"事件循环Event Loop"驱动,所有启动方式本质都是"将协程注册到事件循环中,由循环调度执行"。
# 核心包含两个关键要素 + 一个核心动作:
* 核心要素1:
事件循环是asyncio的核心,相当于一个无限循环的"调度器",负责:
(1) 管理所有可等待对象(协程/任务/Future)的执行顺序
(2) 监听IO事件,如网络请求、文件读写,当某个任务可执行时唤醒它
(3) 切换不同协程的执行,实现并发
把事件循环想象成一个"任务调度中心",所有协程都必须进入这个中心,才能被执行。
* 核心要素2:
可等待对象(Awaitable)------ 被调度的"任务",能被事件循环调度的对象:
(1) 协程对象(async def 函数调用后返回)
(2) 任务Task(asyncio.create_task()包装协程后生成,是可取消的协程)
(3) Future(底层对象,代表异步操作的最终结果)
Task是事件循环直接调度的最小单元,"单纯的协程对象会被自动包装成Task后再执行"。
* 核心动作: 将可等待对象注册到事件循环,并启动循环
所有"启动方式"本质都是在做这两件事:
(1) 把"协程/任务"注册到事件循环中
(2) 启动事件循环,让它开始调度执行这些任务
1.2.1、async run():自动管理事件循环【顶层协程运行】
1、python 3.7+ 新增的最高级 API,是异步编程的标准入口,它封装了事件循环的完整生命周期
2、自动创建事件循环、运行协程、完成后关闭循环,无需手动管理循环生命周期
核心特性:
(1)自动创建新的事件循环,默认使用 asyncio.new_event_loop()
(2)自动将传入的主协程加入事件循环并运行至完成
(3)自动关闭事件循环,即使程序抛出异常,也会优雅关闭
(4)每个程序/主线程中通常只能调用一次 ,不能嵌套使用
python
# 语法:
* asyncio.run(main, debug=False):
- main: 要运行的主协程对象
- debug: 布尔值,默认False,启用调试模式,会输出更多异步操作的日志,如:
- 未完成的Task/Future,协程执行耗时过长的警告,I/O操作的详细日志
python
async def demo_coroutine():
print("开始执行协程(asyncio.run 自动管理循环)")
await asyncio.sleep(1)
print("协程执行结束(asyncio.run 自动管理循环)")
return "执行成功"
if __name__ == "__main__":
# 自动管理事件循环:一行代码启动异步程序
result = asyncio.run(demo_coroutine())
print(f"协程返回结果:{result}")
# 等价于
def my_async_run(coro):
loop = asyncio.new_event_loop()
try:
return loop.run_until_complete(demo_coroutine())
finally:
loop.close()
1.2.2、event loop:手动管理事件循环
手动管理事件循环需要显式完成创建事件循环 → 调度协程 → 运行循环 → 关闭循环四个步骤
核心步骤:
(1)创建事件循环 :通过get_event_loop() 获取当前线程默认循环 或 new_event_loop() 创建新循环
(2)调度协程 :将协程对象封装为任务(loop.create_task()) 或 直接传入协程对象
(3)运行循环 :通过loop.run_until_complete() 运行至指定协程完成 或 loop.run_forever() 永久运行,需手动停止
(4)关闭循环:通过 loop.close() 优雅关闭循环,释放资源
1.2.2.1、loop.run_until_complete():运行至协程完成(最常用)
python
async def demo_coroutine():
print("开始执行协程(手动管理循环 → run_until_complete)")
await asyncio.sleep(1)
print("协程执行结束(手动管理循环 → run_until_complete)")
return "手动执行成功"
if __name__ == "__main__":
# 步骤1:手动创建事件循环
loop = asyncio.get_event_loop()
try:
# 步骤2、3:手动调度并运行协程,直到协程执行完毕
result = loop.run_until_complete(demo_coroutine())
print(f"协程返回结果:{result}")
finally:
# 步骤4:手动关闭事件循环,释放资源
loop.close()
# 验证:循环关闭后无法再使用
print(f"事件循环是否已关闭:{loop.is_closed()}")
1.2.2.2、 loop.run_forever():永久运行(需手动停止)
适用于需要长期运行的异步服务(如TCP服务器),需通过future或task手动触发循环停止
python
async def long_running_coroutine(loop):
print("长期运行协程启动...")
await asyncio.sleep(2)
print("长期运行协程执行完毕,准备停止事件循环")
# 手动停止事件循环,触发loop.run_forever()退出
loop.stop()
if __name__ == "__main__":
# 步骤1:创建事件循环
loop = asyncio.new_event_loop()
# 将循环设置为当前线程的默认循环
asyncio.set_event_loop(loop)
try:
# 步骤2:创建任务并添加到循环
task = loop.create_task(long_running_coroutine(loop))
# 步骤3:永久运行循环(直到 loop.stop()被调用)
loop.run_forever()
# 获取任务结果
print(f"长期协程返回结果:{task.result()}")
finally:
# 步骤4:关闭循环
loop.close()
print(f"事件循环是否已关闭:{loop.is_closed()}")
- asyncio.create_task:封装为Task,立即加入事件循环调度
python
async def coro_func(x):
print(f"执行协程,参数:{x}")
await asyncio.sleep(1)
return x * 2
async def main():
task = asyncio.create_task(coro_func(10)) # 封装为Task,立即调度
await task # 等待完成
print("Task结果:", task.result())
asyncio.run(main())
1.2.2.3、手动管理事件循环报错
- DeprecationWarning: There is no current event loop:event_loop = asyncio.get_event_loop() 警告
python
# 警告产生的核心原因
asyncio.get_event_loop()是一个旧版接口,它的设计初衷是:
获取当前线程已经存在的默认事件循环
# 警告产生的根本原因是:
在调用asyncio.get_event_loop()时,当前线程(主线程)中还没有自动创建/注册任何默认事件循环,该接口在这种情况下的兼容行为即将被废弃,因此抛出DeprecationWarning警告
简单说:
旧版Python会在首次调用get_event_loop()时自动创建一个默认循环,而新版Python更强调"显式创建循环",不再推荐这种"隐式自动创建"的行为,因此给出警告。
方案一:显式创建事件循环 + 绑定当前线程
python
async def demo_coroutine():
print("开始执行协程(显式创建+绑定循环,无警告)")
await asyncio.sleep(1)
print("协程执行结束(显式创建+绑定循环,无警告)")
return "执行成功"
if __name__ == "__main__":
# 步骤1:显式创建新的事件循环(不会触发任何警告)
loop = asyncio.new_event_loop()
# 步骤2:将创建的循环绑定为当前线程的默认事件循环
asyncio.set_event_loop(loop)
try:
# 步骤3:此时再获取当前循环(已存在,无警告)
current_loop = asyncio.get_event_loop()
# 验证:current_loop 和 loop 是同一个对象
print(f"循环是否为同一个:{current_loop is loop}")
# 运行协程至完成
result = loop.run_until_complete(demo_coroutine())
print(f"协程返回结果:{result}")
finally:
# 步骤4:手动关闭循环
loop.close()
print(f"事件循环是否已关闭:{loop.is_closed()}")
方案 2:直接使用 asyncio.new_event_loop() 跳过 get_event_loop(),更简洁
python
async def demo_coroutine():
print("开始执行协程(直接使用new_event_loop,无警告)")
await asyncio.sleep(1)
print("协程执行结束(直接使用new_event_loop,无警告)")
return "执行成功"
if __name__ == "__main__":
# 直接显式创建事件循环,无需获取当前循环,避免警告
loop = asyncio.new_event_loop()
try:
# 直接使用创建的循环运行协程
result = loop.run_until_complete(demo_coroutine())
print(f"协程返回结果:{result}")
finally:
# 手动关闭循环
loop.close()
print(f"事件循环是否已关闭:{loop.is_closed()}")
方案3:抑制警告
python
# 抑制asyncio的DeprecationWarning警告
warnings.filterwarnings("ignore", category=DeprecationWarning, module="asyncio")
1.2.3、coro.send(None) :底层手动启动方式(了解)
这是协程的底层启动方法,直接调用协程对象的 send 方法并传入 None,可以手动触发协程执行。但这种方式不推荐在业务代码中使用(缺乏异常处理、无法优雅等待异步操作),更多用于理解协程底层原理
注意:这种方式无法完整处理异步等待,仅做演示
python
async def simple_coroutine():
print("协程开始执行")
await asyncio.sleep(0.5)
print("协程执行结束")
if __name__ == "__main__":
coro = simple_coroutine()
try:
coro.send(None) # 手动启动协程(底层方式)
except StopIteration as e:
# 协程执行完毕后会抛出StopIteration异常
pass
1.3、Task 任务
任务(Task)是对协程的可调度封装,事件循环通过管理任务来实现协程的并发执行,核心是控制任务的创建、等待、取消、结果获取等。
Task是事件循环直接调度的最小单元 :
(1) 必须 在正在运行 的事件循环中调用,即只能在async def函数内,或事件循环启动后调用
(2)调用后立即返回Task对象,协程不会立即执行,需等待事件循环调度
(3)Task 的执行是并发而非并行:单线程下通过事件循环切换执行,适合 I/O 密集型任务
(4)context 参数用于上下文传递,需配合 contextvars 模块使用
python
# Task任务控制生命周期与结果获取:
* task.get_name() / task.set_name(name): 获取任务名称 / 设置任务名称
* task.cancel(msg=None):
- msg: 取消原因,可选
- 作用: 取消该任务
1. 若"任务已完成",则"不会有任何效果"
2. 若"任务未执行",则"直接标记为取消"
3. 若"任务正在执行",则"会在下次await处抛出CancelledError,需手动捕获处理"
4. 任务内需捕获CancelledError以执行清理逻辑,如关闭资源
* task.cancelled():
- 作用: 判断Task是否被取消
- 仅当 task.cancel() 成功且任务未完成时返回 True
* task.result():
- 作用: 获取Task执行完成后的返回值
1. 若任务未完成、被取消或抛出异常,调用该方法会"重新抛出相应的异常"
- 未完成: InvalidStateError
- 被取消: CancelledError
* task.exception():
- 作用: 获取Task执行过程中抛出的异常
1. 若任务未完成、被取消,调用该方法会"重新抛出相应的异常"
- 未完成: InvalidStateError
- 被取消: CancelledError
3. 若Task正常完成,返回None
* task.done(): 判断任务是否已完成
- 作用: True表示任务已完成(正常结束/抛出异常/被取消),False表示未完成
- 1. 已完成的Task不会被事件循环调度
* task.add_done_callback(callback): 任务完成后执行回调
- callback: 回调函数,必须接收一个Task对象作为参数
- 作用: 为Task添加"完成回调函数",Task结束后,无论成功或失败,都会自动执行该回调
1. 回调函数是同步函数,若需异步操作,需在回调内用loop.call_soon_threadsafe
* task.remove_done_callback(callback): 移除已注册的回调函数
1.3.1、Task对象的核心方法
python
# 自定义回调函数(同步)
def task_callback(task):
print(f"\n===== 回调函数执行 =====")
print(f"任务名:{task.get_name()}")
if task.cancelled():
print("任务状态:已取消")
elif task.exception():
print(f"任务状态:异常,原因:{task.exception()}")
else:
print(f"任务状态:正常完成,结果:{task.result()}")
async def demo_task(delay: float, is_error: bool = False):
try:
print(f"任务 {asyncio.current_task().get_name()} 启动")
await asyncio.sleep(delay)
if is_error:
raise ValueError("主动抛出异常")
return f"延迟 {delay} 秒完成"
except asyncio.CancelledError:
print(f"任务 {asyncio.current_task().get_name()} 被取消,执行清理")
raise
async def main():
# 1. 创建任务并设置名称
task1 = asyncio.create_task(demo_task(1), name="正常任务")
task2 = asyncio.create_task(demo_task(2, is_error=True), name="异常任务")
task3 = asyncio.create_task(demo_task(3), name="取消任务")
# 2. 添加完成回调
for task in [task1, task2, task3]:
task.add_done_callback(task_callback)
# 3. 取消 task3
task3.cancel("手动取消任务")
# 4. 等待任务完成
await asyncio.gather(task1, task2, task3, return_exceptions=True)
# 5. 查看任务状态
print("\n===== 任务最终状态 =====")
print(f"task1 完成:{task1.done()},取消:{task1.cancelled()}")
print(f"task2 完成:{task2.done()},异常:{task2.exception()}")
print(f"task3 完成:{task3.done()},取消:{task3.cancelled()}")
asyncio.run(main())
1.3.2、asyncio.create_task:创建Task任务
用于将协程对象包装为 Task 对象 ,并立即加入当前正在运行的事件循环的调度队列(非阻塞,立即调度),事件循环会在 "当前协程暂停时 " (如遇到 await)调度该 Task 执行,实现多协程并发执行。
python
# 语法参数:
* asyncio.create_task(coro, name=None, context=None): 创建Task任务并立即加入事件循环调度
- coro: 要包装的协程对象
- name: 任务名称,用于调试,可通过"task.get_name()"获取
- context: 上下文对象(contextvars.Context),用于在Task中传递上下文变量(如请求ID、用户信息)
# 特性:
1. 创建后任务会自动在事件循环中运行,"无需额外触发"
2. 返回的 Task 对象本身是"可等待对象",可通过 await task "等待任务完成" 并 "获取协程执行结果"
1.3.2.1、创建多个task并发执行
python
import asyncio
import contextvars
# 定义上下文变量(用于传递请求ID)
request_id = contextvars.ContextVar("request_id", default="unknown")
async def task_func(delay: float):
# 在 Task 中获取上下文变量
print(f"Task {asyncio.current_task().get_name()} - Request ID: {request_id.get()}")
await asyncio.sleep(delay)
return f"Task {asyncio.current_task().get_name()} 完成,延迟 {delay} 秒"
async def main():
# 设置上下文变量
ctx = contextvars.copy_context()
ctx.run(request_id.set, "req-12345")
# 创建Task 并 立即加入事件循环进行调度执行
task1 = asyncio.create_task(task_func(2), name="task-1", context=ctx)
task2 = asyncio.create_task(task_func(1), name="task-2", context=ctx)
# 等待Task完成 并 获取结果
"""
遇到await,main协程暂停,将控制权交给事件循环
"""
res1 = await task1
res2 = await task2
print(f"结果1:{res1}")
print(f"结果2:{res2}")
if __name__ == "__main__":
asyncio.run(main())
1.3.3、loop.create_task:创建Task任务
python
loop.create_task(coro, *, name=None)
1.3.4、asyncio.ensure_future:创建Future/Task(兼容旧版)
Python 3.7之前的通用创建Task方法,Python 3.7+ 后推荐使用 create_task 方法
功能兼容create_task:
(1)若传入协程对象:自动包装为 Task(等效于create_task)
(2)若传入 Future 对象:直接返回该对象
(3)若传入其他可等待对象:适配为Future对象
python
* asyncio.ensure_future(coro_or_future, loop=None):
- coro_or_future: 协程对象/Future对象/其他可等待对象
- loop: 指定事件循环,Python 3.10+ 已弃用,自动使用当前循环
python
async def func():
await asyncio.sleep(1)
return "ensure_future 结果"
async def main():
# 等效于create_task
fut = asyncio.ensure_future(func())
print(type(fut)) # <class 'asyncio.tasks.Task'>
result = await fut
print(result)
asyncio.run(main())
1.3.5、asyncio.TaskGroup():更安全的批量任务管理(推荐)
TaskGroup 是 3.11+ 官方推出的"任务组"工具,用于批量管理 Task 的生命周期,比 gather 更安全、更易用
支持 自动等待所有任务完成、异常自动传播、自动取消未完成任务、支持上下文管理器
python
# 核心方法:
* tg.create_task(coro, name=None, context=None): 在任务组内创建Task,无需手动收集任务
- name: 任务组名称
- context: 任务组的上下文对象
* async with tg: 上下文管理器,进入上下文时创建任务组,退出时自动等待所有任务完成
# 语法格式:
# 退出上下文时,自动等待所有任务完成(或异常终止)
async with asyncio.TaskGroup() as tg:
# 在任务组内创建任务
task1 = tg.create_task(coro1)
task2 = tg.create_task(coro2)
# 注意:
1. TaskGroup 内的任务若抛出异常,会在退出async with时统一抛出,且会立即自动取消所有未完成任务
2. 支持嵌套使用 TaskGroup,任务组内可再创建子任务组
3. 无法获取任务的返回值列表(需手动存储),适合"无需单独处理结果,只需确保所有任务完成"的场景
python
async def task_ok(delay: float):
await asyncio.sleep(delay)
print(f"正常任务完成(延迟 {delay} 秒)")
return delay
async def task_error():
await asyncio.sleep(0.5)
raise ValueError("任务执行失败")
async def main():
results = [] # 手动存储任务结果
try:
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(task_ok(1), name="ok-1")
t2 = tg.create_task(task_ok(2), name="ok-2")
t3 = tg.create_task(task_error(), name="error-1")
results.append(await t1)
results.append(await t2)
results.append(await t3)
except ValueError as e:
print(f"捕获任务组异常:{e}")
print(f"已完成任务的结果:{results}")
# 此时 t2 已被自动取消(因 t3 抛出异常)
asyncio.run(main())
python
async def task(name, delay):
await asyncio.sleep(delay)
if name == "C":
raise ValueError(f"Task {name} failed")
return f"Task {name} done"
async def main():
async with asyncio.TaskGroup() as tg:
# 在任务组中创建任务
task_a = tg.create_task(task("A", 2))
task_b = tg.create_task(task("B", 1))
task_c = tg.create_task(task("C", 3))
# 任务组退出时,所有任务已完成(或被取消)
print(f"Task A result: {task_a.result()}")
print(f"Task B result: {task_b.result()}")
# Task C抛出异常,调用result() 会重新抛出
try:
print(f"Task C result: {task_c.result()}")
except ValueError as e:
print(f"Task C exception: {e}")
asyncio.run(main())
1.3.6、任务 和 事件循环调度 的关系?
事件循环是asyncio的核心调度器,而Task是被事件循环调度执行的最小异步单元。
通俗理解:
- 事件循环:像一个 "任务调度中心 ",负责管理所有任务的生命周期(创建、暂停、恢复、终止),决定哪个任务该在什么时候执行。
- 任务:是对协程的封装 ,让协程具备 "可被调度 " 的能力(比如可以暂停等待 IO、可以被取消、可以获取执行结果)。只有把任务注册到事件循环中,它才会被调度执行。
关键逻辑:
当任务执行到await关键字 时(等待 IO / 休眠 / 其他协程),会主动 "让出 " CPU,事件循环会立刻切换到其他就绪状态 的任务执行;当await的条件满足后(比如休眠结束、IO 响应完成),该任务会被事件循环重新标记为 "就绪",等待下一次调度执行
python
async def async_task(name, sleep_time):
print(f"[{time.strftime('%H:%M:%S')}] 任务{name}:开始执行,准备休眠{sleep_time}秒")
# 此时任务会让出CPU,事件循环切换到其他任务
await asyncio.sleep(sleep_time)
print(f"[{time.strftime('%H:%M:%S')}] 任务{name}:休眠结束,执行完成")
return f"任务{name}结果"
async def main():
# 1. 创建任务(封装协程,使其可被调度)
t1 = asyncio.create_task(task("Task-A", 2))
t2 = asyncio.create_task(task("Task-B", 1))
print(f"[{time.strftime('%H:%M:%S')}] 所有任务已提交到事件循环")
# 2. 等待所有任务完成,await会让主协程暂停,事件循环调度子任务
result1 = await t1
result2 = await t2
print(f"[{time.strftime('%H:%M:%S')}] 所有任务执行完毕,结果:{result1}, {result2}")
# 启动事件循环,asyncio.run会自动创建事件循环、运行主协程、结束后关闭循环
if __name__ == "__main__":
asyncio.run(main())
事件循环启动后,先执行main协程:
1、执行asyncio.create_task()创建t1和t2,这两步只是创建任务并注册到循环 ,任务内的协程代码还没执行
2、执行print("所有任务已提交到事件循环"),输出你看到的第一行日志
3、执行await t1,main协程暂停,事件循环开始调度任务:
4、先调度 t1,执行async_task("Task-A", 2)的代码,打印 "任务 Task-A:开始执行..."
5、t1 执行到await asyncio.sleep(2),让出 CPU,事件循环切换到 t2
6、t2 执行async_task("Task-B", 1)的代码,打印 "任务 Task-B:开始执行..."
7、t2 执行到await asyncio.sleep(1),也让出 CPU,但此时没有其他就绪任务,事件循环进入 "等待" 状态
8、过 1 秒后,t2 的休眠完成,事件循环调度 t2 继续执行,打印 "执行完成" 后 t2 结束
9、又过 1 秒(总共 2 秒),t1 的休眠完成,事件循环调度 t1 继续执行,打印 "执行完成" 后 t1 结束
10、t1 和 t2 都完成后,main协程的 await 条件满足,继续执行,打印最终结果
1.3.7、不创建任务,可直接用事件循环运行协程吗?
核心原理:事件循环可直接运行协程,底层会自动封装为 Task
不手动调用asyncio.create_task()创建任务时,事件循环在执行协程的过程中,会自动将协程包装成临时的 Task 对象来进行调度。
手动创建 Task 是为了更灵活地管理任务(比如取消、获取状态、并发执行多个任务),而直接运行协程则是更简洁的方式,适用于不需要手动管理任务生命周期的场景
1.4、 批量/并发任务调度
1.4.1、asyncio.gather:批量等待任务
用于并发运行多个可等待对象(协程/Task/Future),等待所有对象完成后返回结果列表 ,结果顺序与传入顺序完全一致(即使某个任务先完成) 。当我们需要同时运行多个任务并获取它们的结果时 ,它比 create_task 更加方便。
python
# 语法参数:
* asyncio.gather(*aws, return_exceptions=False):
- 作用: 并发运行多个可等待"Awaitable"对象,等待所有对象完成后,返回一个包含所有结果的列表
- *aws: 任意数量的Awaitable(协程/Task/Future)对象
- return_exceptions:
- False: 默认,任意一个任务抛出异常,立即终止所有任务并向上抛该异常
- True: 异常不会终止其他任务,而是将异常对象作为"结果"存入返回列表
# 核心概念:
* 可接收多个可等待对象
* 可自动将协程转为Task,无需手动调用create_task()
* 自动将它们加入事件循环并调度执行
* 等待所有协程执行完毕后,"按输入顺序返回结果列表"
* 若中途通过asyncio.current_task().cancel()取消,则"所有未完成"的任务都会被取消
* 任意一个任务抛出异常,会立即终止并向上抛出该异常,"其他任务即使已完成也不会返回结果"
# 场景:
* 批量执行多个异步任务并获取所有结果,如"批量接口请求、批量文件处理"
python
async def task_success(name: str, delay: float):
await asyncio.sleep(delay)
return f"{name} 成功,延迟 {delay} 秒"
async def task_failure(name: str, delay: float):
await asyncio.sleep(delay)
raise ValueError(f"{name} 执行失败")
async def main():
# 场景1:所有任务正常执行
tasks_normal = [
task_success("任务A", 2),
task_success("任务B", 1)
]
results_normal = await asyncio.gather(*tasks_normal)
print("正常场景结果:", results_normal)
# 输出:正常场景结果: ['任务A 成功,延迟 2 秒', '任务B 成功,延迟 1 秒']
# 场景2:捕获异常(return_exceptions=True)
tasks_mixed = [
task_success("任务C", 1),
task_failure("任务D", 0.5),
task_success("任务E", 1.5)
]
results_mixed = await asyncio.gather(*tasks_mixed, return_exceptions=True)
print("混合场景结果:", results_mixed)
# 输出:混合场景结果:
# ['任务C 成功,延迟 1 秒', ValueError('任务D 执行失败'), '任务E 成功,延迟 1.5 秒']
# 场景3:异常终止(return_exceptions=False)
try:
results_error = await asyncio.gather(*tasks_mixed)
except ValueError as e:
print("异常场景捕获:", e) # 输出:异常场景捕获: 任务D 执行失败
asyncio.run(main())
python
async def task_func(name, delay):
print(f"任务{name}启动(延迟{delay}秒)")
await asyncio.sleep(delay)
print(f"任务{name}完成")
return f"结果-{name}"
async def main():
# 1. 创建任务,立即调度
t1 = asyncio.create_task(task_func("A", 2))
t2 = asyncio.create_task(task_func("B", 1))
# 2. gather并发等待,按输入顺序返回结果
results = await asyncio.gather(t1, t2)
print("gather结果:", results) # ['结果-A', '结果-B']
asyncio.run(main())
python
# 参数为协程对象
async def my_coroutine(name):
print(f"协程 {name} 开始执行")
await asyncio.sleep(1)
print(f"协程 {name} 执行结束")
return f"{name} 执行结果"
# 定义主协程
async def main_auto():
# 1. 创建多个协程对象
coro1 = my_coroutine("Coroutine-A")
coro2 = my_coroutine("Coroutine-B")
coro3 = my_coroutine("Coroutine-C")
# 2. 使用asyncio.gather()自动启动并且并行执行所有协程
results = await asyncio.gather(coro1, coro2, coro3)
# 3. 输出所有结果(按协程传入顺序返回)
print("\n所有协程执行完毕,结果列表:")
for idx, res in enumerate(results):
print(f"第{idx+1}个协程结果:{res}")
if __name__ == "__main__":
asyncio.run(main_auto())
1.4.2、asyncio.wait:灵活等待任务
python
# 语法参数:
* asyncio.wait(aws, timeout=None, return_when):
- 作用: 等待多个可等待对象完成,返回两个集合:已完成的任务(done)和未完成的任务(pending),支持通过return_when参数控制返回时机。
- done: 已完成(正常/异常/取消)的Task/Future集合
- pending: 未完成的Task/Future集合
- 参数:
- aws: Awaitable对象的可迭代对象(集合,列表等)
- timeout: 超时时间(秒,浮点数/整数),超时后立即返回,未完成的对象存入pending
- return_when=asyncio.FIRST_COMPLETED: 任意一个任务完成就返回
- return_when=asyncio.FIRST_EXCEPTION: 任意一个任务报错就返回,无异常则等价于"ALL_COMPLETED"
- return_when=asyncio.ALL_COMPLETED: 默认,所有任务完成才返回
# 核心概念:
1. 返回的集合是无序的,需遍历获取结果
2. 未完成的任务pending不会自动取消,需手动调用task.cancel()
3. 若return_when=FIRST_EXCEPTION,抛出异常的任务会被放入"done",其余未完成任务放入 pending
4. 单个任务抛出异常不会中断其他任务,异常会被封装在 done 集合的 Task 对象中
# 场景:
1. 灵活控制等待条件,如:"只要有一个任务完成就处理,而非等待全部"
python
import asyncio
async def task_func(name: str, delay: float):
await asyncio.sleep(delay)
if name == "任务Z":
raise ValueError(f"{name} 异常")
return f"{name} 完成"
async def main():
tasks = [
asyncio.create_task(task_func("任务A", 2), name="Task-A"),
asyncio.create_task(task_func("任务B", 1), name="Task-B"),
asyncio.create_task(task_func("任务C", 1.5), name="Task-C")
]
# 场景1:等待第一个完成
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
print("【场景1】第一个完成的任务:")
for task in done:
print(f"- 任务名:{task.get_name()}, 结果:{task.result()}")
# 取消未完成任务
for task in pending:
task.cancel()
print(f"- 取消任务:{task.get_name()}")
# 场景2:等待第一个异常
tasks2 = [
asyncio.create_task(task_func("任务X", 2), name="Task-X"),
asyncio.create_task(task_func("任务Y", 1), name="Task-Y"),
asyncio.create_task(task_func("任务Z", 1.5), name="Task-Z")
]
done2, pending2 = await asyncio.wait(tasks2, return_when=asyncio.FIRST_EXCEPTION)
print("\n【场景2】第一个异常的任务:")
# 抛出异常的任务会被放入done,需要捕获异常来判断
for task in done2:
try:
print(f"- 任务名:{task.get_name()}, 结果:{task.result()}")
except ValueError as e:
print(f"- 任务名:{task.get_name()}, 异常:{e}")
for task in pending2:
task.cancel()
print(f"- 取消任务:{task.get_name()}")
# 场景3:等待所有任务都完成,默认
tasks3 = [
asyncio.create_task(task_func("任务D", 2), name="Task-D"),
asyncio.create_task(task_func("任务E", 1), name="Task-E"),
asyncio.create_task(task_func("任务F", 1.5), name="Task-F")
]
done3, pending3 = await asyncio.wait(tasks3)
print("\n【场景3】所有任务完成:")
for task in done3:
print(f"- 任务名:{task.get_name()}, 结果:{task.result()}")
asyncio.run(main())
1.4.3、asyncio.wait_for:单个任务超时控制
等待单个可等待对象完成 ,若超过指定超时时间,自动取消该对象并抛出TimeoutError
python
# 语法参数:
* asyncio.wait_for(aw, timeout):
- aw: 单个可等待对象(协程/Task/Future)
- timeout: 超时时间(秒,浮点数/整数);若为None,则无超时限制
# 注意:
1. 超时后会自动调用task.cancel()取消任务,任务内会抛CancelledError,若需执行清理逻辑,需在任务内捕获该异常
2. 任务中必须重新抛出CancelledError,否则wait_for不会触发TimeoutError
3. 必须用try-except捕获TimeoutError,否则异常会终止整个协程
4. 适合对单个任务做超时限制,如"接口请求、数据库查询等超时控制"
python
async def slow_task():
try:
print("慢任务开始执行(需要3秒)")
await asyncio.sleep(3)
return "慢任务完成"
except asyncio.CancelledError:
print("慢任务被取消,执行清理逻辑(如关闭连接)")
raise # 必须重新抛出,否则 wait_for 不会触发 TimeoutError
async def main():
try:
# 超时限制为2秒
result = await asyncio.wait_for(slow_task(), timeout=2)
print(result)
except asyncio.TimeoutError:
print("主协程捕获:任务超时")
asyncio.run(main())
1.4.4、asyncio.timeout():超时上下文管理器
为异步操作设置超时,替代 asyncio.wait_for(),支持上下文管理器语法,更灵活。超时则抛出 TimeoutError
可以为单个或多个异步操作设置超时,如接口请求超时、数据库查询超时、批量任务超时等
python
# 语法格式:
* asyncio.timeout(delay, loop=None)
- delay: 超时时间(秒,浮点数)
python
async def long_running_task(delay):
await asyncio.sleep(delay)
return "Task done"
async def main():
# 使用上下文管理器设置超时(3 秒)
try:
async with asyncio.timeout(3):
result = await long_running_task(5)
print(result)
except TimeoutError:
print("Task timed out after 3 seconds")
asyncio.run(main())
1.4.5、asyncio.as_completed:按完成顺序迭代
将一组可等待对象转为迭代器,返回一个迭代器,迭代器会按任务完成的顺序产出已完成的可等待对象,每次迭代 await 即可获取该任务的结果。
python
# 语法格式:
for done_aw in asyncio.as_completed(aws, timeout=None):
result = await done_aw
* asyncio.as_completed(aws, timeout=None):
- aws: 可等待对象的可迭代对象(列表,集合等)
- timeout: 浮点数,可选,超时时间(秒);超时抛TimeoutError
# 注意:
1. 返回的迭代器产出的是"已完成的可等待对象",需 await 才能获取结果
2. 结果顺序 = 任务完成顺序,"先完成的先返回",与gather的"传入顺序"相反
3. 适合批量任务处理,且需要按完成顺序"完成一个处理一个"的场景,如批量下载文件,下载完成一个就保存一个
python
async def download_file(name: str, delay: float):
print(f"开始下载 {name}(延迟{delay}秒)")
await asyncio.sleep(delay)
return f"{name} 下载完成"
async def main():
tasks = [
download_file("文件1", 2),
download_file("文件2", 1),
download_file("文件3", 3)
]
# 按完成顺序处理结果
print("按完成顺序处理")
for done_task in asyncio.as_completed(tasks, timeout=3):
try:
result = await done_task
print(f"处理结果:{result}")
except TimeoutError:
print("任务超时")
asyncio.run(main())
# 处理结果:文件2 下载完成 → (1秒后) → 处理结果:文件1 下载完成 → (1秒后) → 处理结果:文件3 下载完成
1.4.6、gather 和 wait 区别
gather和wait源码内部会对列表中的每个协程执行ensure_future从而封装为Task对象,所以参数可以传递协程对象
| 特性 | asyncio.gather |
asyncio.wait |
|---|---|---|
| 返回值 | 结果列表(顺序与输入一致) | (done, pending) 元组(需手动提取结果) |
| 异常处理 | 可通过 return_exceptions 捕获异常 |
异常需手动处理(done 中的任务调用 result() 抛异常) |
| 灵活性 | 简单易用,适合批量等待所有任务 | 支持按条件返回(如第一个完成),更灵活 |
| 取消任务 | 需手动取消所有子任务 | 可直接处理 pending 中的未完成任务 |
1.5、Event Loop 事件循环
事件循环是 asyncio 的核心调度器,负责:
(1)调度协程 / Task 的执行顺序
(2)处理 I/O 事件(如网络连接、文件读写)
(3)管理定时器(如 asyncio.sleep)
(4)执行同步回调函数
python
# 可以把事件循环当做是一个while循环,这个while循环在周期性的运行并执行一些任务,在特定条件下终止循环
任务列表 = [ 任务1, 任务2, 任务3,... ]
while True:
可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回
for 就绪任务 in 已准备就绪的任务列表:
执行已就绪的任务
for 已完成的任务 in 已完成的任务列表:
在任务列表中移除 已完成的任务
如果 任务列表 中的任务都已完成,则终止循环
1.5.1、asyncio.get_event_loop:获取当前线程的事件循环
若没有则创建一个新的,Python 3.10+ 已标记为过时,推荐用 get_running_loop 或 new_event_loop
1.5.2、asyncio.new_event_loop:创建新的事件循环对象
创建一个新的、未运行的事件循环对象,用于手动管理循环,需手动绑定到当前线程
python
# 语法格式:
* loop = asyncio.new_event_loop()
# 注意:
1. 创建后需手动调用loop.run_until_complete()或loop.run_forever()启动
2. 启动后需手动调用loop.close()关闭
python
async def func():
await asyncio.sleep(1)
print("新循环执行完成")
# 手动创建、启动、关闭循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) # 设置为当前线程的默认循环
try:
loop.run_until_complete(func()) # 启动循环直到任务完成
finally:
loop.close() # 关闭循环
1.5.3、asyncio.get_running_loop:获取当前正在运行的事件循环
获取当前线程中正在运行的事件循环,Python 3.7+ 新增,替代旧版的get_event_loop()
python
# 语法格式:
* loop = asyncio.get_running_loop()
# 注意:
1. 仅能在事件循环运行期间调用,即async def函数内
2. 若当前无运行的循环,抛RuntimeError
python
async def main():
loop = asyncio.get_running_loop()
print(f"当前循环:{loop}")
print(f"循环是否运行:{loop.is_running()}") # True
asyncio.run(main())
1.5.4、loop.run_until_complete(future):运行循环直到任务完成
启动事件循环,直到传入的Future/Task/ 协程对象完成,完成后循环停止(但未关闭)
| 写法 | 适用场景 |
|---|---|
| loop.run_until_complete(coro) | 简单场景:只需要运行单个协程,且不需要在运行中操作这个任务(比如取消、查看状态) |
| task = loop.create_task(coro) + loop.run_until_complete(task) | 复杂场景:需要在协程运行过程中操作 Task(比如取消任务、获取任务状态、添加回调) |
python
# 语法格式:
* loop.run_until_complete(future)
# 注意:
1. 该方法会阻塞主线程,直到任务完成
2. 接收的参数是可等待对象"Awaitable" ------ 它既可以是Task对象,也可以是原始的协程对象
- 如果传入的是协程对象,run_until_complete()会"自动"帮你把协程包装成Task对象,再注册到事件循环中执行
python
async def simple_coro():
print("协程执行中")
await asyncio.sleep(1)
print("协程执行完成")
# 获取事件循环
loop = asyncio.get_event_loop()
try:
# 直接传入协程对象,无需手动创建Task
loop.run_until_complete(simple_coro())
finally:
loop.close()
python
# 如果需要在协程运行中取消它,就必须先创建Task对象
async def long_task():
try:
while True:
print("任务运行中...")
await asyncio.sleep(1)
except asyncio.CancelledError:
print("任务被取消了")
loop = asyncio.get_event_loop()
# 必须先创建Task,才能后续取消
task = loop.create_task(long_task())
# 运行2秒后取消任务
loop.call_later(2, task.cancel)
try:
loop.run_until_complete(task)
finally:
loop.close()
1.5.5、loop.run_forever():永久运行循环
永久启动事件循环,直到手动调用loop.stop()才停止,注意:
(1)停止后需手动调用 loop.close()
(2)stop() 后循环不会立即退出,需等待当前任务完成
python
# 语法格式:
* loop.run_forever()
python
async def task(loop):
await asyncio.sleep(2)
loop.stop() # 停止事件循环
print("Task done and loop stopped")
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# 创建任务并添加到循环
loop.create_task(task(loop))
# 永久运行循环(直到 loop.stop() 被调用)
print("Running loop forever...")
loop.run_forever() # 阻塞直到stop()被调用
print("Loop stopped")
# 关闭循环
loop.close()
1.5.6、loop.stop():停止正在运行的循环
配合run_forever()使用,注意:
(1)仅停止循环,不关闭,关闭需手动调用close()
(2)stop()后循环不会立即终止,而是等待当前任务执行完成。需在回调或任务中调用
(3)停止后,事件循环可通过 run_forever() 或 run_until_complete() 重新运行
1.5.7、loop.close():关闭事件循环,释放所有资源
循环使用完毕后执行,注意:
(1)已关闭的循环无法再次启动,需要重建
(2)未完成的 Task 会被取消
1.5.8、loop.is_running()/loop.is_closed():判断循环状态
- is_running():判断是否正在运行
- is_closed():判断是否已关闭
1.5.9、loop.call_soon:立即调度回调函数
将同步回调函数加入事件循环的 "立即执行队列",事件循环会在下一次迭代时执行该回调
python
# 语法格式
* loop.call_soon(callback, *args):
- callback: 同步函数,不能是协程
- args: 传给回调函数的参数
python
def sync_callback(arg1, arg2):
print(f"回调执行:{arg1}, {arg2}")
async def main():
loop = asyncio.get_running_loop()
# 调度同步回调
loop.call_soon(sync_callback, "参数1", "参数2")
await asyncio.sleep(0.1) # 给循环时间执行回调
asyncio.run(main()) # 输出:回调执行:参数1, 参数2
1.5.10、loop.call_later:延迟delay秒调度回调函数
延迟delay秒后,调度同步回调函数执行
python
# 语法格式:
* loop.call_later(delay, callback, *args):
- delay: 浮点数,延迟时间(秒)
- callback: 同步函数
- *args: 回调参数
python
def sync_callback():
print("延迟1秒的回调执行")
async def main():
loop = asyncio.get_running_loop()
# 延迟1秒执行回调
loop.call_later(1, sync_callback)
await asyncio.sleep(1.5) # 等待回调执行
asyncio.run(main()) # 1秒后输出:延迟1秒的回调执行
1.5.11、loop.call_at + loop.time:指定时间调度回调函数
在指定时间戳 when 调度同步回调函数执行
回调的执行顺序:call_soon 优先于 call_later 和 call_at
python
# 语法格式:
* loop.call_at(when, callback, *args, context=None):
- when: 执行回调的时间戳(浮点数)
- callback: 同步函数
- *args: 回调参数
- context: 用于传递上下文变量
* loop.time(): 获取事件循环内部的时间戳(浮点数,单位秒), 返回的是循环内部的时间,与系统时间无关
python
def sync_callback(name: str):
print(f"同步回调执行:{name},当前时间戳:{loop.time():.2f}")
async def main():
global loop
loop = asyncio.get_running_loop()
# 获取当前循环时间
current_time = loop.time()
# 1. 立即调度回调
loop.call_soon(sync_callback, "立即回调")
# 2. 延迟1秒调度回调
loop.call_later(1, sync_callback, "延迟1秒回调")
# 3. 指定时间戳调度(当前时间+2秒)
loop.call_at(current_time + 2, sync_callback, "指定时间回调")
# 等待回调执行完毕
await asyncio.sleep(2.5)
asyncio.run(main())
1.5.12、loop.run_in_executor:执行同步耗时函数
将同步耗时函数(如文件读写、CPU 密集操作)提交到线程池 / 进程池执行,避免阻塞事件循环,异步获取结果
python
# 语法格式
* loop.run_in_executor(executor, func, *args):
- executor: concurrent.futures.Executor对象,可选;设为None则使用默认线程池
- func: 同步函数,不能是协程
- *args: 传给函数的参数
python
import asyncio
import time
# 同步耗时函数
def sync_heavy_task(n):
print("同步任务开始")
time.sleep(2) # 阻塞,但在线程池中执行,不影响事件循环
return sum(range(n))
async def main():
loop = asyncio.get_running_loop()
# 提交到默认线程池执行
result = await loop.run_in_executor(None, sync_heavy_task, 1000000)
print(f"同步任务结果:{result}")
asyncio.run(main())
1.6、常见简单案例
1.6.1、异步mysql连接
python
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import asyncio
import aiomysql
async def execute(host, password):
print("开始", host)
# 网络IO操作:先去连接 47.93.40.197,遇到IO则自动切换任务,去连接47.93.40.198:6379
conn = await aiomysql.connect(host=host, port=3306, user='root', password=password, db='mysql')
# 网络IO操作:遇到IO会自动切换任务
cur = await conn.cursor()
# 网络IO操作:遇到IO会自动切换任务
await cur.execute("SELECT Host,User FROM user")
# 网络IO操作:遇到IO会自动切换任务
result = await cur.fetchall()
print(result)
# 网络IO操作:遇到IO会自动切换任务
await cur.close()
conn.close()
print("结束", host)
task_list = [
execute('47.93.40.197', "root!2345"),
execute('47.93.40.197', "root!2345")
]
asyncio.run(asyncio.wait(task_list))
1.6.2、异步连接多个redis
遇到IO会切换其他任务,提供了性能
python
import asyncio
import aioredis
async def execute(address, password):
print("开始执行", address)
# 网络IO操作:先去连接 47.93.4.197:6379,遇到IO则自动切换任务,去连接47.93.4.198:6379
redis = await aioredis.create_redis_pool(address, password=password)
# 网络IO操作:遇到IO会自动切换任务
await redis.hmset_dict('car', key1=1, key2=2, key3=3)
# 网络IO操作:遇到IO会自动切换任务
result = await redis.hgetall('car', encoding='utf-8')
print(result)
redis.close()
# 网络IO操作:遇到IO会自动切换任务
await redis.wait_closed()
print("结束", address)
task_list = [
execute('redis://47.93.4.197:6379', "root!2345"),
execute('redis://47.93.4.198:6379', "root!2345")
]
asyncio.run(asyncio.wait(task_list))
1.6.3、执行异步任务的几种常见场景
python
async def coro(session, film_url):
await asyncio.sleep(random.uniform(0.1, 0.5))
async with session.get(film_url) as response:
if response.status != 200:
raise RuntimeError(f"Request failed with status {response.status}")
# return await response.text()
return film_url
async def main(urls):
tasks = []
async with aiohttp.ClientSession() as session:
# 情景一:
print("情景一: create_task + gather")
for index, url in enumerate(urls):
task = asyncio.create_task(coro(session, url), name=f"task-{index + 1}")
tasks.append(task)
try:
res_list = await asyncio.gather(*tasks, return_exceptions=True)
# 遍历结果,区分成功和失败的任务
for idx, res in enumerate(res_list):
task_name = f"task-{idx + 1}"
if isinstance(res, Exception):
print(f"{task_name} 执行失败: {res}")
else:
print(f"{task_name} 执行成功: {res}")
# 取消所有未完成的任务
except asyncio.CancelledError:
for task in tasks:
if not task.done():
task.cancel()
print(f"任务{task.get_name()} 已取消")
# 情景二:
print("情景二: create_task + wait")
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
for task in done:
print(task.result())
for task in pending:
task.cancel()
# 情景三:
print("情景三: 直接运行协程对象,事件循环会将协程对象包装成任务")
for index, url in aenumerate(urls):
task_test = coro(session, url)
print(await task_test)
# 情景四:
print("情景四: 直接运行task,create_task创建任务后,会立即加入到事件循环,会主动调度执行")
"如果需要获取返回值,则通过 await '任务对象' 来获取"
for index, url in enumerate(urls):
task = asyncio.create_task(coro(session, url), name=f"task-{index + 1}")
print(await task)
# 情景五:
print("情景五: 协程对象 + gather")
"gather会将协程对象封装成task,再进行批量调度"
coro_list = []
for index, url in enumerate(urls):
task = coro(session, url)
coro_list.append(task)
res = await asyncio.gather(*coro_list)
print(res)
# 情景六:
print("情景六: task安全组, 比gather更安全,推荐")
print("TaskGroup的结果需要手动收集")
res = []
for index, url in enumerate(urls):
async with asyncio.TaskGroup() as tg:
task = tg.create_task(coro(session, url), name=f"task-{index + 1}")
res.append(await task)
print(res)
if __name__ == '__main__':
url_list = [
"https://www.baidu.com",
"https://movie.douban.com",
"https://www.sina.com.cn",
"https://www.doubao.com/"
]
# 第一种启动方式,常用
# asyncio.run(main(url_list))
# 第二种启动方式
loop = asyncio.new_event_loop()
try:
loop.run_until_complete(main(url_list ))
finally:
loop.close()
# 下面是直接运行协程对象的情景:
async with aiohttp.ClientSession() as session:
for index, url in enumerate(url_list):
# 情景七
print("情景七:直接运行协程")
asyncio.run(coro(session, url))
# 情景八
print("情景八:直接运行协程")
loop = asyncio.new_event_loop()
try:
loop.run_until_complete(coro(session, url))
finally:
loop.close()