
Python 协程,是可以在执行过程中暂停和恢复的特殊函数,让单个线程能够并发处理多个任务。
可以使用 async 和 await 关键字来定义协程函数:
import asyncio
async def task1():
for i in range(3):
await asyncio.sleep(1)
print(f"任务1: 第{i + 1}步")
async def task2():
for i in range(3):
await asyncio.sleep(0.5)
print(f"任务2: 第{i + 1}步")
async def main():
# 同时运行多个协程
await asyncio.gather(
task1(),
task2()
)
asyncio.run(main())some_awaitable 被注册到事件循环中some_awaitable 完成some_awaitable 完成时,事件循环重新激活挂起的协程await 语句后继续执行可见,事件循环(Event loop)是协程调度的核心。
事件循环包含一组待运行的作业。事件循环会从其待处理事项中取出一个作业并唤起它(或称“给予其控制权”),类似于调用一个函数,然后该作业就会运行。 一旦它暂停或完成,它会将控制权返回给事件循环。 然后事件循环会从作业池中选择另一个作业并唤起它。
此过程将无限地重复,事件循环也不停地循环下去。 如果没有待执行的作业,事件循环会足够智能地转入休息状态以避免浪费 CPU 周期,并在有更多工作需完成时恢复运行。
除了官方asyncio库,还有第三方协程库比如trio。
关于事件循环,这里有一个概念关系图,它们都是基于操作系统底层的事件通知机制:

关键字的说明
async def 定义的函数是协程函数,任何函数都可以用 async def 定义,但如果其内部及其调用的函数,没有交出控制权的 await ,一旦其开始运行,直到结束才会交出控制权(默认的return语句交出的)。
调用协程函数,会返回一个协程对象。因此,协程必须显式启动,仅仅创建协程并不能启动它。协程可以在函数体的不同位置暂停和恢复。这种暂停和恢复能力使得异步行为成为可能!
await 语句只能出现在协程函数中,await 只能用于实现了 __await__() 方法的可等待对象,包括协程对象(coroutines)、任务(Task)、未来对象(Future)。
刚才提到的可等待对象是什么呢?
协程对象 : 调用 async def 函数返回的对象;
任务 Task: 是绑定到事件循环的协程,任务还维护一个回调函数列表;
未来对象Future:是一个用来表示状态和结果的状态对象,Task也是通过继承此类获得的状态管理;
await 和 控制权
并不是每个await都会把控制权交回事件循环,await 任务或Future对象状态是交回事件循环的。await 一个协程对象,相当于同步执行,直接用当前的协程运行,并等待其完成。
例1:await 一个coroutine协程
async def plant_a_tree():
await dig_the_hole()
# 与植树相关的其他指令。
...这种情况下,await 不会交回控制权,会直接在当前协程运行dig_the_hole协程,并等待其退出,是同步等待。
例2:await 一个 task任务
async def plant_a_tree():
dig_the_hole_task = asyncio.create_task(dig_the_hole())
# 添加其他任务 ...
# 必须等待的时候等待其完成
await dig_the_hole_task
# 与植树相关的其他指令。
...这种情况下,create_task() 创建任务并将其加入事件循环,此时不会运行,await 会把当前函数添加到dig_the_hole_task任务的回调中,然后控制权交回事件循环,事件循环就会调度dig_the_hole_task,等dig_the_hole_task运行完, await 才会返回 。
注:本例只有一个任务,看上去好像await任务时才会执行,但不是这样的。create_task()时任务就已经加入事件循环了,之后对任何任务的await都会把控制权交回事件循环,事件循环中的任务都有可能被执行。并不是等await某个任务时才会执行这个任务。
例3:等待Future对象
import asyncio
# await future 时,实际上是在等待回调机制
async def set_result_after_delay(future):
print("设置 Future 结果...")
await asyncio.sleep(1)
future.set_result("最终结果")
async def main():
future = asyncio.Future()
# 启动设置结果的任务
asyncio.create_task(set_result_after_delay(future))
print("开始 await future...")
result = await future # 这里会等待回调系统通知完成
print(f"await 完成,得到结果: {result}")
asyncio.run(main())这个例子中首先创建了设置future对象结果的协程,添加到事件循环,等运行到await future时,控制权交回事件循环,set_result_after_delay 开始执行,并设置了future的结果,之后 await future 就返回了。
理清协程和yield的关系
1.从协程中有效地放弃控制权:
从协程中“yield”,方法是 await 一个在其 __await__ 方法中 yield 的对象。
当然,更推荐 await asyncio.sleep(0)
class YieldToEventLoop:
def __await__(self):
yield
async def somefunc():
await YieldToEventLoop()
await asyncio.sleep(0) # 这个更好2.使用 yield from 定义协程(旧式协程,不要再用了):
import asyncio
@asyncio.coroutine
def old_style_coroutine():
yield from asyncio.sleep(1)
return "done"带有 yield 的协程函数,叫异步生成器:
import asyncio
async def async_data_generator():
for i in range(3):
await asyncio.sleep(0.1) # 模拟异步操作
yield i
async def main():
# 必须使用 async for 来迭代
async for item in async_data_generator():
print(item)
# asyncio.run(main())