A Conceptual Overview of asyncio
一开始我不熟悉 Python asyncioPython 的协程用起来需要用asyncio.run()在 Python 3.6 还没有asyncio.run()。asyncio.create_task()为什么报错 No running event loopPython 3.12.3 (main, Jan 8 2026, 11:30:50) [GCC 13.3.0] on linuxType help, copyright, credits or license for more information. import asyncio async def foo(): ...... asyncio.create_task(foo())Traceback (most recent call last):File stdin, line 1, in moduleFile /usr/lib/python3.12/asyncio/tasks.py, line 417, in create_taskloop events.get_running_loop()^^^^^^^^^^^^^^^^^^^^^^^^^RuntimeError: no running event loop协程不会立刻执行调用异步函数创建了一个协程对象。协程对象内部的逻辑不会立刻执行等到事件循环拥有控制权并且启动/继续协程的时候协程的逻辑才会执行。Python 3.12.3 (main, Jan 8 2026, 11:30:50) [GCC 13.3.0] on linuxType help, copyright, credits or license for more information. import asyncio async def foo(): print(bar)... foo()coroutine object foo at 0x720ef6db5a80我们可以看到协程的内部的逻辑没有执行。要执行这个协程我们可以使用asyncio.run()。 asyncio.run(foo())bar在 Python 的协程中要想启动另外一个协程就要用asyncio.create_task()创建一个 Task 并且和事件循环关联起来这个函数会找到当前正在运行的事件循环。一个线程只有一个正在运行的事件循环。asyncio.run()和asyncio.create_task()帮我做了一些事情但是我不知道他到底干什么了。老实说我很讨厌编程语言各种隐式的行为。asyncio.run()相当于是这样几行loop asyncio.new_event_loop()task asyncio.Task(foo(), looploop)loop.run_util_complete(task)run_util_complete 支持 coroutine也可以不手动创建 Task.loop asyncio.new_event_loop()loop.run_util_complete(foo())我们见到了手动创建 Task 的方法可以想象到 create_task 是如何知道放在哪个事件循环上的。def create_task(coro):loop asyncio.get_running_loop()return asyncio.Task(coro, looploop)JavaScript 调用异步函数直接就执行了函数体哪怕你不加 await。JavaScript 异步函数体就相当于传递给 Promise 的回调函数。let promise new Promise(function(resolve, reject) {// this function will executes immediately})让步控制权给事件循环class YieldToEventLoop:def __await__(self):yieldThe only way to yield (or effectively cede control) from a coroutine is to await an object thatyields in its__await__method.在一个对象的__await__方法中 yield 是让步控制权给事件循环的唯一的方式。await object会调用对象的__awit__方法。await coroutine 不会将控制权交给事件循环简单的 await coroutine 就好似 yield from generator。这对熟悉 JavaScript 协程的人是很反直觉的。Python 的经典协程也就是 Python 生成器当作协程使用但是我们没有事件循环。coroutine 指的是 coroutine object。调用一个 async function 会创建一个 coroutine object。await 一个 Task(Future) 对象就会让步控制给事件循环。Task 是 Future 的子类Future 的__await__方法会 yield。注意我们不能直接在异步函数协程函数里面直接写 yield 实现让步控制给事件循环调用函数就会变成异步生成器。我们需要持有 Task 对象并且 awaitIt’s important to be aware that the task itself is not added to the event loop, only a callback to the task is. This matters if the task object you created is garbage collected before it’s called by the event loopTask 对象一定要有变量持有并 await。不然当变量超出作用域被 GC 回收后协程没执行完自己就静悄悄退出了不会报错。Task 对象 GC 回收那么 coroutine 对象也被回收了。说实话我很难理解事件循环没有 Task 对象。但是反过来 JavaScript 的 Promise .then 的时候不就是在注册回调函数吗V8 的微任务队列里面不是保存 Promise 的对象是回调函数啊。import asyncioasync def async_print(str):print(str)async def coro_a():for _ in range(3):await async_print(I am coro_a()!)class YieldToEventLoop:def __await__(self):yieldasync def coro_b():for _ in range(3):print(I am coro_b()!)await YieldToEventLoop() # neccessay to show coro_b() one timeasync def main():task asyncio.create_task(coro_b())await coro_a()asyncio.run(main())# Output:# I am coro_a()!# I am coro_a()!# I am coro_a()!# I am coro_b()!如果读懂了这个例子那也就读懂了整篇笔记或者原文了。main() 函数创建 task 后没有 await task不会让步控制权给事件循环所以 main 函数继续执行。await coro_a 在 await coroutine不会让步控制给事件循环执行 coro_a()。coro_a()await 的也是协程对象也不会让步给事件循环所以连着打印了三次I am coro_a()!。循环执行完毕后main()协程对象执行完了事件循环才拿到控制权开始执行coro_b的逻辑。coro_b刚开始一次协程await YieldToEvnetLoop让步控制给事件循环。因为 main() 函数早已执行完毕退出 task 早已超出作用域被 GC 回收等事件循环再次运行 coro_b 的时候发现对应的 Task 对象已经被 GC 回收了。所以就打印了一次I am coro_b()!。我很好奇这个地方创建回调函数的时候怎么就没有形成闭包呢怎么就没有把 task 对象隐藏在闭包里面呢总结await 协程不会让步控制权await Future 才会让步控制权给 Event Loop。实现协程让步就是在__await__方法中 yield。