python asyncio异步执行

import asyncio

# 定义一个耗时的操作
async def long_running_task(name):
    print(f"{name} started")
    await asyncio.sleep(1)  # 模拟耗时操作
    print(f"{name} finished")

# 定义主函数
async def main():
    task1 = asyncio.create_task(long_running_task("Task 1"))
    task2 = asyncio.create_task(long_running_task("Task 2"))

    # 等待所有任务完成
    await task1
    await task2

    print("All tasks completed")

if __name__ == "__main__":
    # 启动事件循环并运行主函数
    asyncio.run(main())

在这个例子中,我们定义了两个协程long_running_task(),它们分别模拟了两个耗时的操作。在main()函数中,我们创建了这两个协程的任务,并使用await关键字等待它们完成。由于每个协程在遇到await asyncio.sleep(1)时都会暂停自己并交出控制权,所以整个程序并不会被阻塞,而是并发地执行两个任务。

当所有任务完成后,事件循环会退出,程序结束。输出结果应该是"Task 1 started" -> "Task 2 started" -> "Task 1 finished" -> "Task 2 finished" -> "All tasks completed"。注意,尽管任务1和任务2都是同时开始的,但由于模拟的耗时操作不同,它们可能不会按顺序完成。这就是异步编程的一个重要特性:任务的执行顺序并不总是确定的。
让我们详细解释一下上述异步编程例子中主线程和协程在每个阶段做的事情:

主函数定义阶段:
我们定义了一个名为long_running_task的协程,它会打印一条消息表示开始执行,并模拟一个耗时的操作(通过await asyncio.sleep(1))。
定义了主函数main(),在这个函数里我们创建了两个任务(task1和task2),它们分别对应两个不同的long_running_task协程实例。

事件循环启动阶段:
在程序的最后,我们调用了asyncio.run(main())来启动事件循环并运行main()函数。这个操作将整个程序交给事件循环来管理。

协程执行阶段:
事件循环首先遇到create_task(long_running_task("Task 1"))和create_task(long_running_task("Task 2")),它会创建两个协程任务并把它们加入到待处理的任务队列中。
遇到第一个await task1时,事件循环从任务队列中取出task1并开始执行。long_running_task("Task 1")开始运行,并在遇到await asyncio.sleep(1)时暂停自己,将控制权交回给事件循环。
此时,事件循环发现还有另一个任务task2等待执行,于是它开始执行long_running_task("Task 2"),该协程同样会在await asyncio.sleep(1)处暂停。
现在,事件循环已经没有正在运行的任务了,但它有两个等待中的任务。由于这两个任务都是在等待sleep操作完成,所以事件循环可以在这段时间内处理其他事情,比如响应用户输入或者检查网络连接等。

协程恢复阶段:
当await asyncio.sleep(1)的时间到了,操作系统会唤醒相应的协程,让它继续执行。这时,事件循环会发现有可恢复的任务,于是重新调度这些任务。
首先,Task 1被唤醒,继续执行余下的代码,打印"Task 1 finished"。
接着,Task 2被唤醒,也执行余下的代码,打印"Task 2 finished"。

主函数结束阶段:
当所有协程任务都完成后,事件循环会退出。此时,main()函数中的最后一个await task2语句执行完毕,main()函数继续执行,打印"All tasks completed"。

事件循环结束阶段:
asyncio.run(main())返回,事件循环停止运行,程序结束。
通过这个过程,我们可以看到协程如何在不阻塞主线程的情况下并发执行任务,从而提高了程序的效率。

await关键字的作用是挂起当前协程的执行,并将控制权交还给事件循环。当协程遇到await表达式时,它会暂停自己的执行,直到等待的操作完成。

具体来说,在这个例子中,我们使用了await asyncio.sleep(1)来模拟一个耗时的操作。当协程执行到这一行代码时,它会暂停自己并开始等待。此时,事件循环可以调度其他任务进行执行,而不会被阻塞。

一旦await asyncio.sleep(1)操作完成(即等待的时间过去),操作系统会唤醒相应的协程,让它继续执行余下的代码。这样,协程就可以在不阻塞主线程的情况下并发执行任务,从而提高了程序的效率。

总结起来,await关键字的作用主要有两个:

挂起当前协程的执行,让出CPU资源给其他任务。
等待某个操作完成,然后恢复协程的执行。
通过合理地使用await关键字,我们可以编写出高效的异步程序,更好地利用多核CPU和IO设备的性能。

posted @ 2023-12-11 11:29  我是小菜鸡丫丫  阅读(128)  评论(0编辑  收藏  举报