写点什么

Python 使用 asyncio 包实现异步编程方式

  • 2024-08-14
    湖南
  • 本文字数:2875 字

    阅读完需:约 9 分钟

一、异步编程

异步编程是一种编程范式,用于处理程序中需要等待异步操作完成后才能继续执行的情况。


异步编程允许程序在执行耗时的操作时不被阻塞,而是在等待操作完成时继续执行其他任务。


这对于处理诸如文件 I/O、网络请求、定时器等需要等待的操作非常有用。



使用异步编程通常可以带来以下好处:

  • 提高程序效率和性能:异步编程使得程序在执行耗时的 I/O 操作(如网络请求、文件读写、数据库查询等)时不会被阻塞,减少了等待时间,充分利用了系统资源。

  • 改善用户体验:在 Web 开发中,异步编程可以确保服务器在处理大量并发请求时能够快速地响应用户,从而提高了 Web 应用的响应速度和用户体验。

二、async/await 和 asyncio 包

2.1 异步函数的定义

在 Python 中实现异步函数的定义需要两个关键字(async 和 await)。

  • async:async 关键字声明一个异步函数。它可以在执行过程中暂停并允许其他代码执行。当你调用一个异步函数时,它会立即返回一个协程对象而不是实际的结果。异步函数适用于执行耗时的 I/O 操作,例如网络请求、文件读写、数据库查询等。这些操作通常涉及到等待外部资源的响应或者数据的传输,而在等待的过程中,CPU 可以执行其他任务,从而提高程序的效率。

  • await:await 关键字在 Python 中用于等待一个异步操作完成。当调用异步函数时,使用 await 关键字可以暂时挂起当前的异步函数的执行,将 CPU 控制权还给事件循环(Event Loop)。接着事件循环可以将执行权转移到其他任务上,而不是一直等待当前的异步函数完成。当被 await 的异步操作完成后,事件循环会通知原来的异步函数,使得它可以继续执行后续的操作。


在 Python 中异步函数的定义需要同时满足以下两个条件:

  • 使用 async def 关键字声明函数。

  • 函数内部包含异步操作,并且使用了 await 关键字等待异步操作完成。如果一个函数中只使用了 async def 声明,但其中任何异步操作,也没有使用 await 关键字,那么它实际上就是一个普通的同步函数,而不是一个异步函数。

2.2 事件循环

事件循环(Event Loop)是异步编程中负责管理和调度异步任务执行的机制。


事件循环的工作原理类似于一个持续运行的循环,它在每一轮循环中都会执行以下几个步骤:

  • 等待任务就绪: 事件循环会等待所有注册的异步任务就绪,包括等待 I/O 操作完成、等待计时器超时等。

  • 选择就绪任务:一旦有任务就绪,事件循环会选择其中一个任务进行执行。

  • 执行任务:事件循环会执行所选择的就绪任务,直到任务完成或者遇到 await 关键字,需要暂时挂起任务的执行。

  • 挂起任务:如果任务遇到 await 关键字,它会将控制权交还给事件循环,并等待 await 后面的异步操作完成。

  • 继续执行其他任务:在等待 await 的异步操作完成的过程中,事件循环会继续执行其他就绪的任务,从而实现了并发执行的效果。

  • 异步操作完成: 当一个 await 后面的异步操作完成后,事件循环会通知原来的任务,使得它可以继续执行后续的操作。

2.3 asyncio 包

asyncio 包 python 中常用的异步编程框架,这里使用该框架完成一个简单的异步编程案例,具体如下:

import timeimport datetimeimport asyncioasync def async_read_file():    print("async读文件开始:",datetime.datetime.fromtimestamp(time.time()))    await asyncio.sleep(20)    print("async读文件完成:",datetime.datetime.fromtimestamp(time.time()))
def computer():    print("普通计算密集型任务:",datetime.datetime.fromtimestamp(time.time()))    sum=0    for i in range(1000000):        if i%250000==0 and i!=0:            print("普通计算密集型任务正在执行:",datetime.datetime.fromtimestamp(time.time()))        for j in range(500):            sum+=i+j-2*j    print("普通计算密集型任务完成:",datetime.datetime.fromtimestamp(time.time()))
def computer2():    print("普通CPU密集型任务:",datetime.datetime.fromtimestamp(time.time()))    sum=0    for i in range(1000000):        if i%250000==0 and i!=0:            print("普通CPU密集型任务正在执行:",datetime.datetime.fromtimestamp(time.time()))        for j in range(5000):            sum+=i+j-2*j    print("普通CPU密集型任务完成:",datetime.datetime.fromtimestamp(time.time()))
async def asy_main():    task=loop.create_task(async_read_file()) # 创建一个任务,并添加到事件循环,等待执行    task2=loop.run_in_executor(None,computer)# 将普通函数read_file添加到事件循环中,等待执行    task3=loop.run_in_executor(None,computer2)# 将普通函数read_file2添加到事件循环中,等待执行    await task3    await task2    await task
loop=asyncio.get_event_loop() # 创建一个事件循环loop.run_until_complete(asy_main())
复制代码

其执行结果如下:


普通计算密集型任务: 2024-05-15 18:29:19.702689 普通 CPU 密集型任务: 2024-05-15 18:29:19.708280async 读文件开始: 2024-05-15 18:29:19.738654 普通计算密集型任务正在执行: 2024-05-15 18:29:21.441072 普通计算密集型任务正在执行: 2024-05-15 18:29:23.192585 普通计算密集型任务正在执行: 2024-05-15 18:29:24.936979 普通计算密集型任务完成: 2024-05-15 18:29:26.712930 普通 CPU 密集型任务正在执行: 2024-05-15 18:29:32.539679async 读文件完成: 2024-05-15 18:29:39.752731 普通 CPU 密集型任务正在执行: 2024-05-15 18:29:41.813872 普通 CPU 密集型任务正在执行: 2024-05-15 18:29:51.103737 普通 CPU 密集型任务完成: 2024-05-15 18:30:00.433402


从代码运行结果中可以看到,两个计算密集型的任务 task2、task3 和异步函数 task 添加到事件循环上之后,在等待异步操作 task 完成的过程中,CPU 并没有闲着,而是在执行 task2 和 task3 的任务。


Tips:虽然当下的执行结果中写完成了 computer()的计算,后完成了 computer2()的计算,但多次执行上述程序的时候也出现了两个函数交替执行的结果。


为了与上述代码形成对比,执行下述代码:

import asyncioimport datetime
async def async_task(name, delay):    print(f"Task {name} started:",datetime.datetime.now())    await asyncio.sleep(delay)    print(f"Task {name} finished:",datetime.datetime.now())
async def main():    await async_task("A", 2)    await async_task("B", 1)    await async_task("C", 3)
asyncio.run(main())
复制代码


其代码执行结果如下:


Task A started: 2024-05-21 17:45:24.324535Task A finished: 2024-05-21 17:45:26.326109Task B started: 2024-05-21 17:45:26.326250Task B finished: 2024-05-21 17:45:27.327795Task C started: 2024-05-21 17:45:27.327923Task C finished: 2024-05-21 17:45:30.329475


从执行结果上可以看到这三个异步操作是顺序执行的,并没有同时执行。


这是因为在执行 await 后面的异步操作时事件循环中只有一个任务。

用户头像

欢迎关注,一起学习,一起交流,一起进步 2020-06-14 加入

公众号:做梦都在改BUG

评论

发布
暂无评论
Python使用asyncio包实现异步编程方式_Python_我再BUG界嘎嘎乱杀_InfoQ写作社区