asyncio 异步编程

x33g5p2x  于2022-05-05 转载在 其他  
字(2.6k)|赞(0)|评价(0)|浏览(336)

首先了解一下协程,协程的本质就是一条线程,多个任务在一条线程上来回切换,协程的所有切换都是基于用户,只有在用户级别才能感知到的 IO 才会用协程模块来规避,在 python 中主要使用的协程模块是 asyncio,并且基于 async 和 await 关键字的协程可以实现异步编程,这也是目前 python 异步相关的主流技术。

1.事件循环

事件循环它其实是异步编程中的一个非常重要的环节,可以把它当成一个死循环,它会去检查并执行一些代码。

**示例:**伪代码

任务列表 = [ 任务1, 任务2, 任务3,... ]

while True:
    可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回
    
    for 就绪任务 in 已准备就绪的任务列表:
        执行已就绪的任务
    
    for 已完成的任务 in 已完成的任务列表:
        在任务列表中移除 已完成的任务
    
    如果 任务列表 中的任务都已完成,则终止循环

通过上述伪代码就会发现这个事件循环就是可以理解成一个死循环在检查一个列表里的任务,如果列表里面的任务是可执行的,那就去执行这个任务,如果是不可执行(指的是遇到 IO 操作)的,那么在检查的时候就根本检查不到,相当于把 这个任务忽略掉,认为它不需要被执行,让它一直在等待着 IO 请求,当 IO 完成之后在去执行这个任务。

获取和创建事件循环

import asyncio

# 生成和获取一个事件循环
loop = asyncio.get_event_loop()

# 给事件循环添加任务,让事件循环去检测这个任务的状态是否可运行
loop.run_until_complete(任务)

2.async

async 是一个关键字,用于定义一个协程函数。

协程函数:定义函数的时候使用 async def 函数名

协程对象:执行 协程函数() 得到的协程对象。

# 定义一个协程函数
async def func():
    pass

# 调用协程函数,返回一个协程对象
result = func()

调用协程函数的时候,函数内部的代码不会执行,只会返回一个协程对象。

如果想要运行协程函数内部代码,必须要将协程对象交给事件循环来处理。

import asyncio

async def func():
    print('这是一个协程函数!')
    
    
result = func

# 方式一:
loop = asyncio.get_event_loop()   # 生成一个事件循环
loop.run_until_complete( result ) # 将协程对象添加到事件循环执行

# 方式二:python 3.7 之后使用,本质上还是和上面一样,但是比较简单
asyncio.run( result )

3.await

await 也是一个关键字,它主要是在当前任务1遇到 IO 操作的时候切到其他没有 IO 操作的任务2去执行,让事件循环可以去执行其他任务,当任务1的 IO 操作执行完后再切换回来执行 await 之后的内容。

await 的后面只能加可等待的对象(协程对象、Task对象 ....)

示例:

import asyncio

async def others():
    print('others -----> start')
    await asyncio.sleep(2)
    print('others -----> end')
    return '返回值'

async def fun():
    print('执行协程函数内部代码!')
    
    # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。
    response = await others()	# 它会等有返回值了才会继续往下执行
    print('IO请求结束,结果为:', response)

asyncio.run(fun())

# 输出:
执行协程函数内部代码!
others -----> start
others -----> end
IO请求结束,结果为: 返回值

从上面这个示例可以看出来 await 就是等待对应后面的值得到结果之后,在向下继续执行!

由于在这个示例中事件循环列表中只有一个任务,所以在 IO 等待时无法演示切换到其他任务的执行效果,在程序中如果想要创建多个任务对象,需要使用 Task 对象来实现。

4.Task对象

Task 用于并发调度协程,在事件循环中添加多个任务。

本质上是将协程对象封装成 Task 对象,并将该协程加入事件循环,同时追踪协程的状态。

**示例1:**通过asyncio.create_task(协程对象)添加任务。

import asyncio

async def func(i):
    print(i, '--->start')
    await asyncio.sleep(1)
    print(i, '--->end')
    return f'返回值{i}'

async def main():
    print('main start')

    # 创建 Task 对象并添加到事件循环中
    task1 = asyncio.create_task(func(1))
    task2 = asyncio.create_task(func(2))

    print('main end')
	
    # 此处await会自动切换执行其他任务。例如:task1,task2
    ret1 = await task1
    ret2 = await task2
    print(ret1, ret2)

asyncio.run(main())

# 输出:
main start
main end
1 --->start
2 --->start
1 --->end
2 --->end
返回值1 返回值2

**示例2:**通过asyncio.wait(协程对象列表)添加任务,在它的源码中会通过 ensure_future 把每个协程封装成 Task 对象。

import asyncio

async def func(i):
    print(i, '--->start')
    await asyncio.sleep(1)  # 当遇到IO操作挂起当前协程并切换其他协程
    print(i, '--->end')
    return f'返回值{i}'

task_list = [func(1), func(2)]

# 如果设置了 timeout 值,则意味着此处最多等待的秒,完成的协程返回值写入done中,未完成的写入pending
done, pending = asyncio.run(asyncio.wait(task_list, timeout=None))

# 输出:
1 --->start
2 --->start
1 --->end
2 --->end

学习之旅

相关文章