2. 异步编程基础回顾:async/await、事件循环、协程与任务

在深入数据库迁移异步任务接口之前,必须牢固掌握 Python 异步编程的核心概念。本章将系统回顾 async/await 语法、事件循环(Event Loop)、协程(Coroutine)与任务(Task),为后续的异步数据库操作打下坚实基础。

2.1 核心概念对比

概念 本质 关键特征 类比(餐厅)
协程(Coroutine) 可暂停/恢复的函数 由 async def 定义;调用返回协程对象;不立即执行 厨师手中的菜谱——可随时暂停,等食材到位再继续
任务(Task) 被事件循环调度的协程包装器 通过 asyncio.create_task() 创建;并发执行;可取消 分配给特定厨师的具体订单——独立跟踪进度
事件循环(Event Loop) 异步运行时调度器 管理所有任务;监听 I/O 事件;分配执行权 餐厅主管——决定哪位厨师在什么时间做什么菜
async/await 语法 声明与挂起的关键字 async 定义协程;await 挂起等待另一个协程 厨师说"等汤煮沸"——暂停当前操作,去切菜

2.2 事件循环(Event Loop)——异步引擎

事件循环是异步编程的核心调度器。它不断重复以下步骤:

  1. 轮询:检查是否有就绪的 I/O 事件(如网络数据到达、文件读取完成)。
  2. 调度:将执行权交给等待该事件的协程。
  3. 执行:协程运行直到遇到下一个 await 或完成。
  4. 挂起:协程挂起时,事件循环记录其状态,继续处理其他任务。
关键理解:事件循环是单线程的。它通过协作式多任务实现并发——协程主动让出控制权(await),而非被操作系统抢占。这意味着协程中的耗时 CPU 计算会阻塞整个循环。
事件循环调度协程与任务流程 事件循环调度流程 事件循环 (Event Loop) 任务队列 Task A (协程包装) Task B (协程包装) Task C (协程包装) I/O 就绪队列 socket 可读 文件读取完成 定时器到期 当前执行协程 await asyncio.sleep(1) 单线程内协作式多任务:协程主动 await 让出控制权,事件循环调度其他就绪任务

2.3 协程(Coroutine)与 async/await 语法

协程是异步编程的基本执行单元。使用 async def 定义的函数返回一个协程对象,该对象必须被事件循环调度才能执行。

2.3.1 基础语法示例

import asyncio

# 定义一个协程函数
async def fetch_data(url: str) -> dict:
    print(f"开始请求: {url}")
    # 模拟网络 I/O 等待,await 挂起当前协程
    await asyncio.sleep(1)
    print(f"完成请求: {url}")
    return {"url": url, "status": 200}

# 直接调用协程函数返回协程对象,不会执行
coro = fetch_data("https://api.example.com/users")
print(type(coro))  # <class 'coroutine'>

# 正确执行方式:在事件循环中运行
async def main():
    result = await fetch_data("https://api.example.com/users")
    print(result)

asyncio.run(main())
💡 易错点:await 只能在 async def 定义的协程内部使用。在普通函数中调用 await 会引发 SyntaxError。协程必须通过 await、asyncio.run() 或 create_task() 驱动执行。

2.4 任务(Task)——并发执行的关键

任务(asyncio.Task)是协程的并发包装器。通过 asyncio.create_task() 将协程包装为任务后,事件循环会自动调度它,无需显式 await 即可开始执行。

2.4.1 任务 vs 直接 await 对比

方式 代码 执行行为 适用场景
直接 await result = await coro 顺序执行:等待协程完成才继续下一行 依赖前一步结果的串行逻辑
创建任务 task = asyncio.create_task(coro) 并发执行:立即调度,不阻塞当前协程 多个独立 I/O 操作并行加速
任务 + await result = await task 等待已创建的任务完成,获取结果 需要并发启动后统一收集结果

2.4.2 并发任务实战示例

import asyncio
import time

async def query_database(query_id: int) -> str:
    """模拟数据库查询"""
    await asyncio.sleep(1)  # 模拟 I/O 等待
    return f"Query-{query_id} result"

async def main():
    start = time.time()

    # 创建 5 个并发任务
    tasks = [
        asyncio.create_task(query_database(i))
        for i in range(5)
    ]

    # 等待所有任务完成,收集结果
    results = await asyncio.gather(*tasks)

    elapsed = time.time() - start
    print(f"耗时: {elapsed:.2f}s")  # 约 1 秒,而非 5 秒
    print(f"结果: {results}")

asyncio.run(main())
⚠️ 任务管理要点:
  • 创建的任务必须被 await 或通过 asyncio.gather() 等待,否则任务可能在程序退出时被静默取消。
  • 使用 task.cancel() 可以取消正在运行的任务,被取消的任务会抛出 asyncio.CancelledError。
  • 任务对象提供 .done()、.result() 等方法用于状态检查。
协程与任务生命周期 协程与任务生命周期 async def 定义 协程函数 调用协程函数 返回协程对象 await coro 顺序执行 create_task() 并发调度 执行中 任务完成 await 获取结果 任务取消

2.5 异步上下文管理器与异步迭代器

在数据库迁移场景中,异步上下文管理器(async with)和异步迭代器(async for)是管理数据库连接和游标的核心工具。

2.5.1 异步上下文管理器

class AsyncDatabaseConnection:
    async def __aenter__(self):
        print("打开数据库连接")
        self.conn = await create_connection()
        return self.conn

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("关闭数据库连接")
        await self.conn.close()

# 使用
async with AsyncDatabaseConnection() as conn:
    result = await conn.execute("SELECT 1")

2.5.2 异步迭代器

class AsyncResultSet:
    def __init__(self, data):
        self._data = data
        self._index = 0

    def __aiter__(self):
        return self

    async def __anext__(self):
        if self._index >= len(self._data):
            raise StopAsyncIteration
        # 模拟异步获取下一行
        await asyncio.sleep(0.1)
        value = self._data[self._index]
        self._index += 1
        return value

# 使用
async for row in AsyncResultSet([1, 2, 3]):
    print(row)

2.6 本章小结

核心要点回顾:
  • 协程是异步函数,通过 async def 定义,await 挂起等待。
  • 事件循环是单线程调度器,管理所有协程和任务的执行顺序。
  • 任务(create_task)实现并发,适合多个独立 I/O 操作并行执行。
  • 异步上下文管理器(async with)和异步迭代器(async for)是数据库连接和结果集操作的标准模式。
  • 所有异步代码必须在事件循环中运行,入口为 asyncio.run(main())。