python3从入门到精通(五): pyhhon协程之asyncio模块(异步IO)(一)

一、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()         
相关推荐
Java程序员威哥2 小时前
SpringBoot4.0+JDK25+GraalVM:云原生Java的性能革命与落地指南
java·开发语言·后端·python·云原生·c#
大柏怎么被偷了2 小时前
【Linux】信号
linux·运维·服务器
liu_sir_2 小时前
android9.0 amlogic 遥控器POWER按键的假待机的实现
开发语言·git·python
Jia ming2 小时前
大小端模式:字节顺序的奥秘
linux·运维·服务器
Zach_yuan2 小时前
Linux 线程入门到理解:从 pthread 使用到线程库底层原理
linux·运维·服务器
TonyLee0172 小时前
Python与Matlab计算效率对比
python·matlab
2401_841495642 小时前
【Python高级编程】学习通签到统计工具
python·pandas·gui·tkinter·pyinstaller·数据统计·exe程序
不会kao代码的小王2 小时前
深信服超融合 HCI 核心技术解析:aSV、aSAN 与 aNET 的协同架构
运维·服务器·网络·数据库·github
小二·2 小时前
Python Web 开发进阶实战:可持续计算 —— 在 Flask + Vue 中构建碳感知应用(Carbon-Aware Computing)
前端·python·flask