bigywater.tech
Python · 2

await 到底暂停了什么:协程、Task 和 Future 的关系

对比await两个裸协程和await两个Task的耗时差距,验证Task是在create_task那一刻就被调度上事件循环的,不是等你await才开始跑。

0.4s vs 0.2s
裸协程顺序await vs Task并发的耗时
Task⊂Future
验证的类继承关系
PENDING→done
Task的状态流转

做的事:同样等0.2秒两次,await裸协程 vs await Task

协程对象本身不会自己运行,得有人把它交给事件循环。

代码async def say(x, delay): await asyncio.sleep(delay) return x # 写法1:直接await两个裸协程对象 coro1 = say('a', 0.2) coro2 = say('b', 0.2) r1 = await coro1 r2 = await coro2 # 写法2:先create_task,再await task1 = asyncio.create_task(say('a', 0.2)) task2 = asyncio.create_task(say('b', 0.2)) r1 = await task1 r2 = await task2

验证:两种写法,耗时差了一倍

同样是等两个0.2秒,耗时天差地别。

结果await两个裸协程(顺序await): 耗时 0.4023s create_task后再await两个Task: 耗时 0.2011s

直接 await coro1 时,协程是在这一刻才开始执行;执行完(0.2秒后)才轮到 await coro2 开始,两个0.2秒叠加成了0.4秒。而 asyncio.create_task() 的效果是:在调用的那一刻,就已经把这个协程包装成一个Task对象,立刻安排到事件循环上开始跑——这跟你什么时候去 await 它没关系。所以两个Task是同时被创建、同时开始跑的,总耗时只要0.2秒。

这是一个很容易踩的坑:以为"await就是让它开始跑",但其实裸协程要等你await才开始执行,Task则是create_task那一刻就开始执行了。如果想要多个操作并发执行,必须先用create_task(或asyncio.gather、ensure_future)把它们都安排上事件循环,而不是写一堆await在那里排队。

验证:Task就是Future的子类,状态会流转

Future是"一个未来会有结果的占位符",Task是"负责真正跑一个协程"的Future。

验证issubclass(asyncio.Task, asyncio.Future) → :True type(task).__mro__ → (_asyncio.Task, _asyncio.Future, object) 刚create_task完,还没await: task1._state → :PENDING task1.done() → :False await之后: task.done() → :True task.result() → :拿到协程的返回值

Task是Future的子类——Future本身只是一个"占位符",记录一个操作有没有完成、结果是什么;Task在这基础上加了"怎么去推进一个协程往前走"的逻辑。观察到的状态流转是 PENDING(还没跑完)到 done()=True(跑完了,可以拿 result())——你 await 一个Task,本质就是在等它的状态变成完成态。

分步演示:await暂停的是什么

每一步都是本文上面用Python 3.14跑出来的原始结果。

裸协程要等await才开始跑,Task在create_task那一刻就开始了
这一步新验证的结果

    本文用到的工具

    • 本机 Python 3.14.7
    • asyncio.create_task / issubclass / __mro__:验证Task与Future的关系
    京ICP备2026067502号-1京公网安备11010502063487号