Python协程asyncio异步编程笔记分享
1.事件循环
可以理解成为一个死循环,去检查任务列表中的任务,如果可执行就去执行,如果检查不到就是不可执行的,那就忽略掉去执行其他可执行的任务,如果IO结束了(比如说去百度下载图片,下载完了就会变成可执行任务)再去执行下载完成之后的逻辑
#这里的任务是有状态的,比如这个任务已经完成或者正在执行或者正在IO等待 任务列表 = [ 任务1, 任务2, 任务3,... ] while True: 可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回 for 就绪任务 in 可执行的任务列表: 执行已就绪的任务 for 已完成的任务 in 已完成的任务列表: 在任务列表中移除 已完成的任务 如果 任务列表 中的任务都已完成,则终止循环
#在编写程序时候可以通过如下代码来获取和创建事件循环。 import asyncio loop = asyncio.get_event_loop() #将任务放到任务列表,让事件循环去检测任务的状态(是否可运行,是否IO) loop.loop.run_until_complete(任务)
2.协程和异步编程
协程函数,定义形式为 async def 的函数。
协程对象,调用 协程函数() 所返回的对象。
# 定义一个协程函数 async def func(): pass # 调用协程函数,返回一个协程对象(内部代码不会执行) result = func()
2.1 基本使用
程序中,如果想要执行协程函数的内部代码,需要 事件循环 和 协程对象 配合才能实现
示例1:
import asyncio async def func(): print("协程内部代码") # 调用协程函数,返回一个协程对象。 result = func() # 方式一 # loop = asyncio.get_event_loop() # 创建一个事件循环 # loop.run_until_complete(result) # 将协程当做任务提交到事件循环的任务列表中,协程执行完成之后终止。 # 方式二 # 本质上方式一是一样的,内部先 创建事件循环 然后执行 run_until_complete,一个简便的写法。 # asyncio.run 函数在 Python 3.7 中加入 asyncio 模块, asyncio.run(result)
2.2 await
await+可等待对象(协程对象,Future,Task对象)
await是一个只能在协程函数中使用的关键字,用于遇到IO操作时挂起 当前协程(任务),当前协程(任务)挂起过程中 事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换回来执行await之后的代码。
import asyncio async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。 # 当前协程挂起时,事件循环可以去执行其他协程(任务)。 #response是IO耗时结束后拿到的结果 response = await asyncio.sleep(2) print("IO请求结束,结果为:", response) result = func() asyncio.run(result)
结果
执行协程函数内部代码
IO请求结束,结果为: None
#这里返回None表示这个好事没有啥意义,如果你是下载了一张图片成功后会返回一个结果
示例2:
import asyncio async def others(): print("start") await asyncio.sleep(2) print('end') return '返回值' async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。 response = await others() print("IO请求结束,结果为:", response) asyncio.run( func() )
执行结果:
执行协程函数内部代码
start
end
IO请求结束,结果为: 返回值
示例3:
import asyncio async def others(): print("start") await asyncio.sleep(2) print('end') return '返回值' async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。 #await等待有返回值才会向下执行 response1 = await others() print("IO请求结束,结果为:", response1) response2 = await others() print("IO请求结束,结果为:", response2) asyncio.run( func() )
执行结果:
执行协程函数内部代码
start
end
IO请求结束,结果为: 返回值
start
end
IO请求结束,结果为: 返回值
下一步依赖上一步的结果时使用await,但如果有其他任务依然会切换到其他任务去执行
2.3 Task对象
在事件循环中添加多个任务。
Tasks用于并发调度协程,通过asyncio.create_task(协程对象)的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行。
除了使用 asyncio.create_task() 函数以外,还可以用低层级的 loop.create_task() 或 ensure_future() 函数。
不建议手动实例化 Task 对象。
本质上是将协程对象封装成task对象,并将协程立即加入事件循环,同时追踪协程的状态。
注意:asyncio.create_task() 函数在 Python 3.7 中被加入。
在 Python 3.7 之前,可以改用低层级的 asyncio.ensure_future() 函数。
示例1:
import asyncio async def func(): print(1) await asyncio.sleep(2) print(2) return "返回值" async def main(): print("main开始") # 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 task1 = asyncio.create_task(func()) # 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 task2 = asyncio.create_task(func()) print("main结束") # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。 # 此处的await是等待相对应的协程全都执行完毕并获取结果 ret1 = await task1 ret2 = await task2 print(ret1, ret2) asyncio.run(main())
执行结果:
main开始
main结束
1
1
2
2
返回值 返回值
实例2:
import asyncio async def func(): print(1) await asyncio.sleep(2) print(2) return "返回值" async def main(): print("main开始") # 创建协程,将协程封装到Task对象中并添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 # 在调用 task_list = [ asyncio.create_task(func(), name="n1"), asyncio.create_task(func(), name="n2") ] print("main结束") # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。 # 此处的await是等待所有协程执行完毕,并将所有协程的返回值保存到done # 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中(比如下面的Timeout=1,要下载的图片是两秒,设为1秒就会执行失败,done内为空,而pending中就是执行失败的)。 done, pending = await asyncio.wait(task_list, timeout=None) print(done, pending) asyncio.run(main()) 执行结果: main开始 main结束 1 1 2 2 { <Task finished name='n1' coro=<func() done, defined at C:/Users/xuan.li/Desktop/unidevopss/py3x64/ibuildmaster/apps/PROD/tests.py:513> result='返回值'>, <Task finished name='n2' coro=<func() done, defined at C:/Users/xuan.li/Desktop/unidevopss/py3x64/ibuildmaster/apps/PROD/tests.py:513> result='返回值'> } set()
示例3:
import asyncio async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。 response = await asyncio.sleep(2) print("IO请求结束,结果为:", response) coroutine_list = [func(), func()] # 错误:coroutine_list = [ asyncio.create_task(func()), asyncio.create_task(func()) ] # 此处不能直接 asyncio.create_task,因为将Task立即加入到事件循环的任务列表, # 但此时事件循环还未创建,所以会报错。 # 使用asyncio.wait将列表封装为一个协程,并调用asyncio.run实现执行两个协程 # asyncio.wait内部会对列表中的每个协程执行ensure_future,封装为Task对象。 done,pending = asyncio.run( asyncio.wait(coroutine_list) )