Python 现代开发学习笔记

(八)事件循环与多个任务的推进

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 因为准备好而继续。网络数据就绪、计时完成或事件发出后,等待的任务重新获得推进机会。

默认调度下A与B先进入、暂停,再依次恢复,等待期间主线程不忙等

asyncio.sleep 不让当前线程原地睡眠等待,它把计时交给循环。time.sleep 则阻塞调用它的线程;放进事件循环线程时,循环在这段时间也不能推进其他任务。第十二节会把已有阻塞函数接到线程中。协程与任务

笔记正文与全部题解 · Ctrl / ⌘ K