(八)事件循环与多个任务的推进
1. 两次直接等待仍然是顺序执行
# 项目文件:demos/tasks_order.py
# 对照直接 await 与先建立两个 Task;结果列表的顺序都由调用者明确保存。
import asyncio
async def read_title(title, trace):
trace.append(title + " 开始")
await asyncio.sleep(0)
trace.append(title + " 结束")
return title
async def main():
serial = []
first = await read_title("A", serial)
second = await read_title("B", serial)
print("顺序", [first, second], serial)
concurrent = []
a = asyncio.create_task(read_title("A", concurrent))
b = asyncio.create_task(read_title("B", concurrent))
results = await asyncio.gather(a, b)
print("任务", results, concurrent)
if __name__ == "__main__":
asyncio.run(main())顺序一行是 ['A 开始', 'A 结束', 'B 开始', 'B 结束']。任务一行中两个开始先发生,再分别结束;本例使用默认任务调度和 sleep(0),可以跟着图中的准备队列逐步观察。
create_task 把协程交给当前循环调度,返回 Task,先保存引用,再等待结果。gather 等待这一组操作,按传入参数的顺序排列结果。Task 由事件循环推进,前面线程池的 Future 由工作线程完成,它们的结果接口相似,执行来源不同。
2. 从准备队列到等待中的任务
同一个事件循环线程一次推进一段 Python 代码。A 遇到尚未完成的等待,保存暂停位置;B 因为准备好而继续。网络数据就绪、计时完成或事件发出后,等待的任务重新获得推进机会。
asyncio.sleep 不让当前线程原地睡眠等待,它把计时交给循环。time.sleep 则阻塞调用它的线程;放进事件循环线程时,循环在这段时间也不能推进其他任务。第十二节会把已有阻塞函数接到线程中。协程与任务