K 的一隅

Python Python 语言核心

await 把控制权交回去:协程如何暂停与恢复

async def 定义协程函数,调用得到协程对象;await 在可等待对象上挂起并把控制权交还事件循环;协程须由 Task 或 asyncio.run 调度才会执行。

20 分钟阅读 更新于 2026-07-28
本文目录

def fetch(): return 1 调用即执行;async def fetch(): return 1 调用 执行函数体——你得到 协程 对象,像未启动的子程序。必须交给 事件循环(经 asyncio.runTask)才会从入口往下跑;遇到 await暂停,把 控制权 交还 调度,I/O 或 Future 就绪后再从暂停处 恢复。这一章只谈 协程 机制本身,不谈 gather 等模式(下一章)。

async def:协程函数 vs 协程对象

async def 定义 协程函数(coroutine function)。调用它产生 协程 对象(coroutine object), 运行体内代码:

python
import asyncio
import inspect


async def greet(name: str) -> str:
    print(f"inside greet({name!r})")
    return f"hi, {name}"


coro = greet("Ada")
print(type(coro).__name__)                  # coroutine
print(inspect.iscoroutine(coro))            # True
print(inspect.iscoroutinefunction(greet))   # True
coro.close()

对比普通函数:

python
def sync_greet(name: str) -> str:
    print(f"sync {name}")
    return f"hi, {name}"


print(sync_greet("Bob"))
# sync Bob
# hi, Bob

在同步上下文里只写 greet("Ada") 而不 await,会得到 coroutine 对象并可能 RuntimeWarning: coroutine 'greet' was never awaited。这不是语法糖能自动修复的——协程 必须被 调度

生命周期:创建 → 调度 → 关闭

阶段发生什么
coro = f()分配 协程 帧,代码未执行
Task / asyncio.runloop 驱动到第一个 awaitreturn
await 可等待对象暂停,保存局部变量与指令位置
可等待对象 done恢复,继续下一条语句
return / 未捕获异常协程 关闭,Task set_resultset_exception
python
import asyncio


async def trace() -> None:
    print("1 enter")
    await asyncio.sleep(0)
    print("2 resumed")
    await asyncio.sleep(0)
    print("3 end")


async def main() -> None:
    await asyncio.create_task(trace())


asyncio.run(main())

输出:

1 enter
2 resumed
3 end

每次 await asyncio.sleep(0) 都是 让出点:注册定时器,事件循环 跑其它 ready 回调,到期后再 恢复 打印下一行。帧上的局部变量在 暂停 期间保留——像线程栈,但仍在同一条 OS 线程上。

await 的语义:挂起与恢复

await expr 要求 exprAwaitable协程TaskFuture,或实现 __await__ 的对象):

python
import asyncio


async def inner() -> int:
    await asyncio.sleep(0.01)
    return 7


async def outer() -> None:
    print("before await")
    x = await inner()
    print("after await", x)


asyncio.run(outer())

时间线(概念):

outer 开始 → 打印 before → 进入 inner → inner await sleep → outer 与 inner 均挂起
→ sleep 到期 → inner return 7 → outer 恢复 → 打印 after await 7

await 做两件事:把当前 协程 登记为「子结果未就绪的等待者」;把 控制权 还给 事件循环。子结果就绪后,loop 调度协程await 表达式处继续,并把子结果作为表达式值。

若子结果 已 done(例如已完成的 Future),await 不挂起,直接取值——这对性能与语义都重要:不要假设每次 await 都会让出。

只有 awaitables:普通调用不会变异步

python
import asyncio
import time


def blocking() -> int:
    time.sleep(0.1)
    return 1


async def wrong_idea() -> int:
    # await blocking()  # TypeError
    return blocking()  # 能返回 1,但阻塞整个 loop


async def right() -> int:
    return await asyncio.to_thread(blocking)


asyncio.run(right())

async def 不会把调用的同步函数变非 阻塞;只有 await 真正的 async I/O 或 to_thread 等桥接,才不占 调度 线程。

协程必须被调度:三种启动方式

python
import asyncio


async def work(tag: str) -> None:
    print(tag)


# 1) 脚本入口
asyncio.run(work("via-run"))

# 2) create_task 并发
async def main() -> None:
    asyncio.create_task(work("via-task"))
    await asyncio.sleep(0)


asyncio.run(main())

# 3) 直接 await —— 串行
async def serial() -> None:
    await work("first")
    await work("second")


asyncio.run(serial())

asyncio.run(main_coro) 内部把 main_coro 包成 Task 并跑到 done——这是 协程事件循环 的默认桥梁。create_task 则在已有 async 上下文里 并发 启动子 协程;只 await 则不并发,只是嵌套调用。

调度 的典型 bug:

python
import asyncio


async def orphan() -> None:
    print("never scheduled")


coro = orphan()
coro.close()

与生成器:像,但协议不同

CPython 里 协程 对象基于生成器机制扩展,但 语义 不同:

生成器协程
定义def + yieldasync def + await
驱动.send() / for仅 loop 通过 await
返回值StopIteration.value写入 Task / Future
目的惰性迭代I/O 协作 调度
python
def gen():
    yield 1
    yield 2


g = gen()
print(next(g), next(g))  # 1 2


async def co():
    await asyncio.sleep(0)
    return 3


async def use_co() -> None:
    print(await co())


asyncio.run(use_co())  # 3

async def 里写 yield 会变成 异步生成器async for 协议),不是普通 协程。学习 asyncio 时只需记住:await 交还 控制权事件循环,不是交还给调用 async def 的同步函数。

协程帧里保存什么

暂停 时,解释器保存:局部变量、指令偏移、内部 await 栈(嵌套 await 时)。因此 协程 可以 await 深层调用,而不额外占 OS 线程:

python
import asyncio


async def depth(n: int) -> int:
    if n <= 0:
        return 0
    await asyncio.sleep(0)
    return 1 + await depth(n - 1)


async def main() -> None:
    print(await depth(4))  # 4


asyncio.run(main())

每层 await sleep(0) 允许 loop 穿插其它 Task;递归过深仍占内存——极端场景应改循环。

async with 与 async for:语法糖里的 await

async with 调用对象的 __aenter__ / __aexit__(二者必须是 可等待 的);async for 驱动 异步迭代器__anext__。它们仍是 协程 代码路径上的 await,只是语法隐藏了:

python
import asyncio


class AsyncCtx:
    async def __aenter__(self) -> str:
        await asyncio.sleep(0)
        return "entered"

    async def __aexit__(self, *args: object) -> None:
        await asyncio.sleep(0)
        print("left")


async def main() -> None:
    async with AsyncCtx() as v:
        print(v)


asyncio.run(main())
# entered
# left

读 trace 时把 async with 展开成两次 await 有助于理解 调度 点。

未 awaited 协程与 GC

协程 对象若既未 Task 也未 await,析构时可能警告。显式 coro.close() 可提前释放帧并注入 GeneratorExit。在框架里一般由 Task 生命周期管理;手写脚本要注意别「创建 coroutine 又丢弃」。

与 Task、Future 的衔接

create_task(coro)协程 绑到 TaskFuture 子类)。协程 return vTask set_result(v);未捕获异常 → set_exceptionawait task 就是 await 那个 Future 侧的结果。因此 协程 章节与 Future 章节是同一枚硬币的两面:协程 写逻辑,Future/Task 写「完成态如何通知 loop」。

常见误解

误解一:「async def 在后台线程跑。」 仍在 loop 线程,除非 to_thread

误解二:「函数里调 async 函数不用 await。」 得到未执行 coroutine,逻辑错误。

误解三:「await 等于 yield。」 await 针对 Awaitable 与 loop 调度yield 针对迭代协议。

误解四:「协程一定比线程轻,所以可以无限创建。」 轻量但有 调度 与内存成本;百万 idle Task 要慎重。

误解五:「asyncio.run 里可以再 asyncio.run。」 嵌套 asyncio.run 在已有 running loop 会失败;用 await 调子 协程

嵌套 await 与「谁让出」

python
import asyncio


async def a() -> int:
    print("a: start")
    await asyncio.sleep(0.01)
    print("a: end")
    return 1


async def b() -> int:
    print("b: start")
    x = await a()
    print("b: got", x)
    return x + 1


async def c() -> None:
    print("c: start")
    y = await b()
    print("c: got", y)


asyncio.run(c())

输出顺序固定为 c start → b start → a start → a end → b got 1 → c got 2**await a()** 在 b 的帧里 暂停 ba 跑完 b 才继续——调用栈逻辑仍是深度优先,只是 aawait sleep 时把 控制权 交给 事件循环,允许其它 Task 插入(本例没有其它 Task)。

若在同一层用 create_task(a()) 而非 await a()b 不会等 a 的返回值才往下走——启动等待 再次分离(下一章重点)。

协程的 close、throw 与调试

协程 对象有 .close().throw(exc)(少用)。.close()GeneratorExit 路径上清理;.throw暂停 点注入异常。框架代码偶尔用;日常 Task.cancel() 更常见。

调试「卡在哪」:看 Task 名、栈里最后一个 await 所在的库(例如等 socket、等 Semaphore)。asyncio.current_task()协程 内取当前 Task

python
import asyncio


async def whoami() -> None:
    t = asyncio.current_task()
    print(t.get_name() if t else None)


async def main() -> None:
    asyncio.create_task(whoami(), name="inner")
    await asyncio.sleep(0)


asyncio.run(main())
# inner

从同步心智迁移:四条规则

  1. async def 调用不执行 —— 先 create_taskawait
  2. await 是唯一协作让出点(外加 async with/for 里的隐式 await)。
  3. 普通函数调用仍可能阻塞 —— 与是否 async def 无关。
  4. 并发来自多个 Task 交错,不是来自 async def 语法本身

用这四条审阅现有代码,比背 API 表更快发现「假 async」。

与线程模型对照(仅一点)

线程里「等结果」常 阻塞future.result()协程 里用 await,等的是 Future done 事件,线程可 调度 其它 Task。不要把 await 理解成「把线程挂起」——挂起的是 协程,不是 OS 线程;loop 线程仍在 调度 ready 队列。

生成器 yield from 与 async 的分叉(辅助)

早期 asyncio@asyncio.coroutine + yield from协程;3.5+ async def / await 成为标准。旧语法已移除。今天若在读老项目,看到 yield from asyncio.sleep(...), mentally 替换成 await asyncio.sleep(...) 即可——控制权 交还 loop 的语义不变。

同一函数内多个 await 点

python
import asyncio


async def pipeline() -> str:
    print("step1")
    await asyncio.sleep(0.01)
    print("step2")
    await asyncio.sleep(0.01)
    print("step3")
    return "done"


async def interleave() -> None:
    asyncio.create_task(pipeline())
    for i in range(3):
        print("other", i)
        await asyncio.sleep(0.005)


asyncio.run(interleave())

pipelineinterleaveawait 点交错——证明 单线程 上多 Task事件循环 调度 穿插,而非并行执行 pipelineinterleave 的字节码。

async def 不是「异步返回值类型」

python
import asyncio


async def f() -> int:
    return 42


async def main() -> None:
    x = await f()
    print(type(x), x)


asyncio.run(main())
# <class 'int'> 42

返回类型仍是 int,不是「Future[int]」。Future调度 层对象;协程 return 的值经 Task 写入 Futureawait 侧看到普通 Python 值。

协程对象在内存里是什么

协程 对象是 CPython 里的 coroutine 类型,基于生成器扩展,带 cr_frame**cr_await** 等字段。**await x** 在字节码层调用 GET_AWAITABLE,再驱动 awaitable__await__ 协议。你不必背字节码,但应知道:暂停 保存的是 ,不是新线程——因此 协程 切换成本远低于 OS 线程,也受 GIL 约束在同一进程内。

只在 async 函数里能写 await

**await** 语法仅允许出现在 async def 函数体中(及 async with/for 脱糖后的内部)。在普通 defawait 是语法错误。这强制「会 挂起 的代码」与「同步代码」在语法层分离,减少误把 阻塞 调用写进 async 路径的概率——尽管仍拦不住 time.sleep

协程不返回 Future,Task 才包 Future

python
import asyncio


async def g() -> int:
    return 1


coro = g()
print(asyncio.isfuture(coro))  # False
t = asyncio.create_task(g())
print(asyncio.isfuture(t))     # True
t.cancel()

**g()**协程;只有 create_task(或 ensure_future)才得到 Future/Task 语义的对象供 调度 追踪。

并发不是 async def 的语法自带

python
import asyncio


async def work(n: int) -> int:
    await asyncio.sleep(0.1)
    return n


async def fake_parallel() -> None:
    # 没有并发:顺序 await
    print(await work(1))
    print(await work(2))


async def real_parallel() -> None:
    t1 = asyncio.create_task(work(1))
    t2 = asyncio.create_task(work(2))
    print(await t1, await t2)


asyncio.run(real_parallel())

**async def** 只定义 可暂停函数并发 来自多个 Task 交错 调度,来自 gather 汇合,不来自关键字本身。

yield from 时代遗留(了解即可)

Python 3.4–3.10 过渡期存在 @types.coroutine / yield from 写法驱动 Future。今天统一 async def / awaitawait 可读性更好,且与 async generatorasync with 同一套语法。读老博客若见到 yield from asyncio.sleep(0),等价于 modern await asyncio.sleep(0)

异步生成器:第三个 async 物种

**async def** 里写 yield 得到 异步生成器,用 async for 消费,不是 普通 协程

python
import asyncio


async def agen():
    yield 1
    yield 2


async def main() -> None:
    async for x in agen():
        print(x)


asyncio.run(main())
# 1
# 2

async for 内部多次 **await __anext__。别把 async generator 当成 create_task 的对象——它用于 流式 数据,不是「跑完一个 Future 结果」。

调试:coroutine was never awaited

泄漏 协程 对象会 RuntimeWarning。原因常见:忘了 await、忘了 create_task、或条件分支里创建了 coroutine 却未 调度。修复:保证每个 async def() 调用路径最终 awaitcreate_task + 生命周期管理,或 coro.close()

与 Future 章节的闭合

协程 是写逻辑的单位;Future/Task调度 与完成态的单位。await协程 绑在 Futurepending 上;set_result协程 恢复事件循环 在两者之间轮转 就绪队列。三章合读:loop 轮转Future 状态协程 暂停——asyncio 的主干就齐了。

调用栈与 Task 边界

await 嵌套再深,OS 线程仍一条;调试器里看到多层 coroutine 帧是正常的。create_task 切开 调用栈parentchild 并发,栈不再父子包含——parentawait gatherchildFuture

return 与 StopIteration(实现细节)

协程 return v 在内部触发 StopIteration(v)Task 捕获并把 v set_resultFuture。裸 协程.send(None) 驱动时可见 StopIteration;日常 await 只看到返回值。

async 函数是一等公民

协程函数 可赋值、当参数、闭包捕获——与 def 相同,只是调用产物是 协程 而非立即执行:

python
import asyncio
from typing import Callable, Awaitable


async def apply(f: Callable[[], Awaitable[int]]) -> int:
    return await f()


async def main() -> None:
    async def inner() -> int:
        return 7
    print(await apply(inner))


asyncio.run(main())
# 7

类型标注:Coroutine vs Awaitable

**async def f() -> T** 调用得 Coroutine[Any, Any, T];参数常标注 Awaitable[T] 以接受 Task/Future。静态类型帮助区分「同步 callable」与「要 await 的对象」。

练习式读码:从 main 向下追 await

读任意 async 项目:从 async def mainasyncio.run 入口开始,列出每个 create_taskawait。标记每个 await 等的是 sleep、I/O Future 还是 Task——练几次后 pending 卡死的位置会一眼可见。

顶层 await 与 REPL

CPython 3.8+ REPL 支持 顶层 await,内部替你 get_event_loop 驱动 协程——方便实验,不是生产入口。脚本仍用 asyncio.run

协程与 functools.partial

partial(async_fn, arg) 仍返回 协程函数 调用后的 协程调度 前仍是 coro = partial(...)(create_task(partial(...)())。不要 partial(create_task, coro) 混淆 启动 时机。

多层 async def 与测试

测试常 await func_under_test() 而非 asyncio.run 包每个用例——pytest-asyncio 提供 loop fixture。无论哪种,协程 必须被 驱动,否则 never awaited

错误:在 sync 里 asyncio.run 嵌套

python
import asyncio


async def inner() -> int:
    return 1


def outer() -> int:
    return asyncio.run(inner())


# outer()  # 可用
# async def bad():
#     asyncio.run(inner())  # 在 running loop 里嵌套 run → 错

asyncio.run 独占「创建-运行-销毁 loop」;在 async 函数里应 await inner()

协程泄漏的代价

await协程 持有帧与闭包引用,可能泄漏大对象。create_task 后不 await 同理——Task 引用 协程 帧直到 done。fire-and-forget 必须 强引用 + 异常 loggingTaskGroup 汇合

Future:一句话

协程 写「怎么做」;Future 写「结果何时就绪」;Task 把二者绑在一起交给 事件循环 调度

协程局部变量在 await 前后存活

python
import asyncio


async def demo() -> None:
    big = list(range(1000))
    await asyncio.sleep(0)
    print(len(big))


asyncio.run(demo())
# 1000

await 暂停 不释放 局部变量——长生命周期 Task holding 大对象会增加内存。必要时 del 或缩小 Task 范围。

async def 与staticmethod / classmethod

@staticmethod async def 合法:调用 Cls.static()协程,仍须 await/Taskclassmethod 同理。实例方法 async def method(self) 调用 obj.method()协程,不是 coroutine function 再包一层。

理解 await 协议

自定义 Awaitable 实现 __await__ 返回 iteratoryield fromasyncio 内部 Task 驱动。Future__await__pending yield 控制权给 Taskdonereturn 结果。扩展库时用 asyncio.Future 子类较少,多数 compose 现有 Future

对比:同步 def 里调 async —— 边界情况

python
import asyncio


async def aio() -> int:
    return 5


def sync_caller() -> int:
    return asyncio.run(aio())


print(sync_caller())  # 5

sync 入口 asyncio.run 是桥梁;async 入口 await 是桥梁——不要混用导致 嵌套 loop

协程帧深度与 asyncio 并发上限

单进程 thousands Task 可行,但每个 Task 一个 协程 帧 + Future 开销。await 点多 ≠ 自动 并发create_task 数量与 Semaphore 才定义 in-flight 上限。性能调优看 Task 数、pending Future 数、loop slow callback,不是 async def 个数。

事件循环Future 三句口诀

loop 轮转 readyFuture pendingset_result协程 await 把自身 挂起Future 上。背口诀不如画一张你自己的请求时序图——画完 async 就不神秘。

附录:async def 调用链逐步表

代码得到什么是否执行体下一步
async def f协程函数调用 f()
f()协程对象Taskawait
create_task(f())Task是(异步)await task 可选
await f()结果 T是(异步)
`asyncio.run(f())结果 T进程级入口

表背下来,80% 的「协程没跑」问题可秒定位。

多个 await 同一 coroutine 对象?

python
import asyncio


async def once() -> int:
    print("run")
    return 1


async def main() -> None:
    c = once()
    # await c; await c  # 第二次 await 同一 coroutine 对象 → 错
    await asyncio.gather(once(), once())


asyncio.run(main())
# run
# run

协程 对象 一次性——await 消耗它。要两次结果,调两次 once()reuse TaskTaskawait 一次取结果,doneawait 仍得同值 via Future 语义)。

async 闭包与循环变量(经典坑)

python
import asyncio


async def main() -> None:
    tasks = []
    for i in range(3):
        async def work() -> None:
            print(i)  # 闭包捕获 i,循环结束 i=2
        tasks.append(asyncio.create_task(work()))
    await asyncio.gather(*tasks)


asyncio.run(main())
# 2 2 2

sync 闭包相同;用 default arg async def work(i=i) 修复——async 不免疫 LEGB 闭包陷阱。

协程与装饰器

@decoratorasync def 时,装饰器须返回 async defawait 兼容的 callable@sync_decoratorasync 常破坏 await 链——要用 async def wrapper 并在 wrapper 里 await func()

从 def 迁移到 async def 的检查单

  • 每个 async def 调用是否 awaitcreate_task
  • 是否仍有 sync blocking I/O?
  • 并发 是否 create_task/gather,而非假 async 顺序 await
  • 测试是否用 pytest-asyncioasyncio.run 驱动?

TaskFuture 的最终关系图(文字)

协程函数 → 调用 → 协程对象create_taskTask(Future) → loop 驱动await挂起 → 底层 Future set_result协程 恢复returnTask done。整条链上 协程 是「可暂停函数体」;Future 是「完成通知」;loop 是「谁在什么时候运行哪一段」。

读者自查:协程概念是否真懂

async def 调用为何不打 print?awaitreturn 区别?为何 time.sleepasync def阻塞create_taskawait coro() 并发差在哪?协程 对象能否 await 两次?——能答即掌握本章。

与事件循环、Future 的交叉索引

协程 awaitFuture pendingloop 调度 其他 Taskset_result协程 恢复async def alone 不够;三角关系缺一角就 卡住

常见面试式追问(自测)

  • 协程线程 栈谁轻?协作抢占 区别?
  • async generatorcoroutine 区分?
  • asyncio.run 能否在 FastAPI 路由里再调?

答案都在前文;自测加深 机制 记忆,非背题。

写库 vs 写应用

暴露 async defAwaitable应用create_task/gather 组织 并发 内避免 阻塞应用Semaphore/timeout 策略。角色不同,协程 用法侧重不同。

最后一轮机制巩固:await 展开成两步

概念上 x = await aw 等价于:

  1. awdonex = 结果(或 异常)。
  2. pending挂起 当前 协程,登记 awaw done恢复x = 结果。

async def 编译器生成 状态机 字节码实现这两步——你写 await,解释器维护 指令偏移

为何普通函数不能 await

await 需要 当前帧协程 帧,以便 挂起def 帧不支持 yield from awaitable协程 协议——语法层禁止,减少 阻塞 def 误用 await 的混乱。

协程教学常见一张图(文字版)

调用 async 函数 → coroutine 对象 → create_task → Task

                               事件循环 ready 队列

                               运行到 await → 挂起

                               Future done → 恢复 → return

背图不如自己 debug 单步跟一次 asyncio.run(main())——机制肌肉记忆

合并阅读建议

协程 章回答「async def 调用后为何不跑、await 干什么」;与 Future 章连读:await 的就是 Future/Taskpending。**与 事件循环 章连读:await控制权 交还 loop三章合读 后再看 模式 章的 create_task/gather,顺序最省认知负荷。

手写测试:coroutine 必须被驱动

python
import asyncio
import warnings


async def silent() -> None:
    pass


with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter("always")
    c = silent()
    del c
    # 可能 RuntimeWarning: coroutine was never awaited

实验提醒:协程 不是「调用即运行」——调度 是显式义务。

async/await 与语言演化

async def / await 进入语法后,生成器风格 coroutine 退出历史。协程 成为与 defclass 并列的一等语法类别——但 运行时 仍依赖 事件循环 调度,语法便利不替代 机制

结束语前的检查表

  • async def() 得到什么?为何不执行?
  • await 谁能 await
  • create_taskawait coro() 区别?
  • 协程async generator 如何分?
  • asyncio.runawaitasync main 里如何分工?

五项打勾,协程 章可以收工。

深度:async def 编译结果

async def 函数体被编译为含 CO_COROUTINE 标志的 code object;调用时 types.coroutine 包装为 coroutineawait 对应 GET_AWAITABLE + YIELD FROM 路径(概念上)。return3.3+ coroutineStopIteration(value) 传值给 Task

协程与 Iterator 协议边界

协程 不是 Iterator——不能 for x in coro();须 awaitTaskasync generator 才有 __aiter__混淆 coroutine 与 generator 是初学者高频 TypeError 来源。

结束补充

协程 章与 Future 章、loop 章、模式 章构成 Python asyncio 最小完备集;缺任何一角,pending hung 或 阻塞 loop 时都会无从查起。

协程章节完整复述(一页纸)

async def 定义 协程函数,调用得 协程对象,不自动执行;须 asyncio.runcreate_task 调度await 仅对 Awaitable,在 pending挂起 并把 控制权 交还 事件循环returnTask 写入 Futureasync generatorcoroutine 不同。背下这段,never awaited阻塞 loop80% 根因可快速归类。

与 JavaScript async function 的单向对比(一句)

async def 调用也不执行体,与 JS async function() 类似;但 Python 协程 必须被 asyncio loop 驱动,没有 浏览器 那种 自动 microtask 队列——Task/asyncio.run 是显式 调度 入口。

温故:协程相关的四个「何时」

何时产生 coroutine? async def() 调用。何时执行? Task scheduleawait 驱动。何时挂起? await pending Awaitable何时结束? return / 未捕获异常 / cancel。四个 何时 串起来,就是 单条 Task 的生命周期叙事。

温故:async def 与 def 的对照

defasync def
调用立刻执行体coroutine,不执行
等待 I/O阻塞 线程(sync)await 挂起 协程
并发线程/进程Task + loop
返回值直接 Tawait 后得 T

async def 不是 def超集——是 不同 调度 契约下的函数形式。

收束

协程async def 调用后的「可暂停执行体」;await 是暂停按钮;Taskasyncio.run 是播放按钮。整章若只记一句,记:调用 async 函数不会跑,调度才会跑。

建议在 REPL 里 async def f(): return 1f()type(f())await f()(顶层)各试一次,把 协程对象调度 的因果关系刻进肌肉记忆。写 async 代码时先问「这一行是 启动等待 还是 阻塞」,比死记 API 更少踩坑。下一章 模式 会把 Taskgather 放到真实 并发 场景里。通读后应能区分 协程对象Task 何时创建、何时 await。这是 asyncio 语法层最容易混淆、也最值得花时间夯实的一点。建议对照本文 async def 调用链逐步表 做一次自测默写。能不看表说出从 f()await task 的每一步,本章目标即达成。与 Future 章合读时,重点盯住 await 如何把 协程 挂起pendingFuture 上——这是 async/await 机制的核心铰链。以上机制均可在 CPython 3.11+ 本地直接验证,无需第三方库。通读 asyncio 四章后再接 Web 或爬虫项目,体会最深。机制优先,API 次之。本章至此收工,后续见系列第 27 篇。

小结

async def 定义 协程函数;调用产生 协程,须 Taskasyncio.run 调度await可等待对象暂停 并把 控制权 交还 事件循环,完成后 恢复。普通同步调用不会因 async def 变非 阻塞。下一篇讲工程模式:create_taskgathercancelSemaphore