【一】asyncio 模块使用基础
-
asyncio 模块是 Python 中实现异步的一个模块,该模块在 Python3.4 的时候发布
-
async 和 await 关键字在 Python3.5 中引入。
-
因此,想要使用asyncio模块,建议 Python 解释器的版本不要低于 Python3.5 。
【二】事件循环
-
所谓的事件循环,我们可以把它当作是一个 while 循环,这个 while 循环在循环生命周期内运行并执行一些任务,在特定的条件下结束循环。
-
在编写程序的时候可以通过如下代码来获取和创建事件循环:
import asyncio loop = asyncio.get_event_loop()
【三】协程函数和协程对象
【1】什么是协程函数
-
首先我们来看一下协程函数
-
-
什么是协程函数呢?
-
-
直白的讲,定义为如下形式的函数
-
我们可以称之为协程函数,如下代码所示:
# 使用 async 声明的函数就是协程函数 async def fn(): pass
【2】什么是协程对象
-
所谓的协程对象就是调用协程函数之后返回的对象,称之为协程对象
-
注意:调用协程函数时,函数内部的代码不会执行,只会返回一个协程对象
# 使用 async 声明的函数就是协程函数 async def fn(): pass # 调用携程函数得到的对象就是协程对象 res = fn() print(res) # <coroutine object fn at 0x1029684a0>
【四】协程函数应用
【1】基本应用
-
在编写程序的时候,如果想要执行协程函数内部的代码,通过 函数名() 调用函数是不可以的,需要 事件循环 和 协程对象 配合才能实现
import asyncio async def fn(): print('协程函数内部的代码') # 执行协程代码的方式一 def main_first(): # 调用协程函数,返回一个协程对象 res = fn() # todo:1、创建一个事件循环 loop = asyncio.get_event_loop() # todo:2、将协程当作任务提交到事件循环的任务列表中,协程执行完成之后终止 loop.run_until_complete(res) # 执行协程代码的方式二 def main_second(): # 调用协程函数,返回一个协程对象 res = fn() # 解析:第二种方式在本质上和第一种方式是相同的,其内部先创建事件循环,然后执行 run_until_complete # 但是要注意:该方式只支持 Python3.7+ 的解释器,因为该方式在 Python3.7 加入的。 asyncio.run(res) if __name__ == '__main__': main_first() main_second()
-
这个过程可以简单理解为:
-
-
将 协程函数 当做任务添加到 事件循环 的任务列表
-
然后事件循环检测列表中的协程函数 是否已准备就绪(默认可理解为就绪状态)
-
如果准备就绪则执行其内部代码。
-
【2】await关键字
-
await 是一个 只能 在协程函数中使用的关键字,用于当协程函数遇到IO操作的时候挂起当前协程(任务)
-
当前协程挂起过程中,事件循环可以去执行其他的协程(任务)
-
当前协程IO处理完成时,可以再次切换回来执行 await之后的代码
(1)示例1
import asyncio async def fn(): print('协程函数内部的代码') # 遇到IO操作之后挂起当前协程(任务),等IO操作完成之后再继续往下执行。 # 当前协程挂起时,事件循环可以去执行其他协程(任务) response = await asyncio.sleep(2) # 模拟遇到了IO操作 print(f'IO请求结束,结果为:{response}') def main(): # 调用协程函数,返回一个协程对象 res = fn() # 执行协程函数 asyncio.run(res) if __name__ == '__main__': main() ''' 运行结果: 协程函数内部的代码 IO请求结束,结果为:None '''
(2)示例2
import asyncio async def fn(): print('协程函数内部的代码') # 遇到IO操作之后挂起当前协程(任务),等IO操作完成之后再继续往下执行。 # 当前协程挂起时,事件循环可以去执行其他协程(任务) response = await asyncio.sleep(2) # 模拟遇到了IO操作 print(f'IO请求结束,结果为:{response}') def main(): # 调用协程函数,返回一个协程对象 res = fn() # 执行协程函数 asyncio.run(res) if __name__ == '__main__': main() ''' 运行结果: 协程函数内部的代码 IO请求结束,结果为:None '''
(3)示例3
import asyncio async def other_tasks(): print('start') await asyncio.sleep(2) # 模拟遇到了IO操作 print('end') return '返回值' async def fn(): print('协程函数内部的代码') # 遇到IO操作之后挂起当前协程(任务),等IO操作完成之后再继续往下执行。 # 当前协程挂起时,事件循环可以去执行其他协程(任务) respnse1 = await other_tasks() print(f'IO请求结束,结果为:{respnse1}') respnse2 = await other_tasks() print(f'IO请求结束,结果为:{respnse2}') def main(): # 调用协程函数,返回一个协程对象 cor_obj = fn() # 执行协程函数 asyncio.run(cor_obj) if __name__ == '__main__': main() ''' 运行结果: 协程函数内部的代码 start end IO请求结束,结果为:返回值 start end IO请求结束,结果为:返回值 '''
(4)小结
-
上述的所有实例都只是创建了一个任务
-
-
即:事件循环的任务列表中只有一个任务
-
所以在IO等待时无法演示切换到其他任务效果。
-
-
在程序中想要创建多个任务对象
-
-
需要使用Task对象来实现。
-
3】Task 对象
-
Tasks 用于并发调度协程
-
通过 asyncio.create_task(协程对象) 的方式创建 Task 对象
-
这样可以让协程加入事件循环中等待被调度执行。
-
除了使用 asyncio.create_task() 函数以外
-
还可以用低层级的loop.create_task() 或 ensure_future() 函数。并且不建议手动实例化 Task 对象。
-
本质上是将协程对象封装成 Task 对象
-
并将协程立即加入事件循环,同时追踪协程的状态。
-
注意事项:
-
-
asyncio.create_task() 函数在 Python3.7 中被加入。
-
在 Python3.7 之前,可以改用低层级的
-
asyncio.ensure_future() 函数。
-
(1)协程运行方式一
-
async.run() 运行协程
-
async.create_task()创建task
import asyncio async def other_tasks(): print('start') await asyncio.sleep(2) # 模拟遇到了IO操作 print('end') return '返回值' async def fn(): print('fn开始') # 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 task1 = asyncio.create_task(other_tasks()) # 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 task2 = asyncio.create_task(other_tasks()) print('fn结束') # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。 # 此处的await是等待相对应的协程全都执行完毕并获取结果 response1 = await task1 response2 = await task2 print(response1, response2) def main(): asyncio.run(fn()) if __name__ == '__main__': main() ''' 运行结果: fn开始 fn结束 start start end end 返回值 返回值 '''
(2)协程运行方式二
import asyncio async def other_tasks(): print('start') await asyncio.sleep(2) # 模拟遇到了IO操作 print('end') return '返回值' async def fn(): print('fn开始') # 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 task_lis = [ asyncio.create_task(other_tasks()), asyncio.create_task(other_tasks()), ] print('fn结束') # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。 # 此处的await是等待所有协程执行完毕,并将所有协程的返回值保存到done # 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中。 done, pending = await asyncio.wait(task_lis, timeout=None) print(f"done :>>>> {done}") print(f"pending :>>>> {pending}") def main(): asyncio.run(fn()) if __name__ == '__main__': main() ''' fn开始 fn结束 start start end end done :>>>> {<Task finished name='Task-2' coro=<other_tasks() done, defined at /Users/dream/Desktop/PythonProjects/12并发编程/02协程/01.py:4> result='返回值'>, <Task finished name='Task-3' coro=<other_tasks() done, defined at /Users/dream/Desktop/PythonProjects/12并发编程/02协程/01.py:4> result='返回值'>} pending :>>>> set() '''
(3)获取协程返回值
-
async.gather()获取返回值
import asyncio async def other_tasks(): print('start') await asyncio.sleep(2) # 模拟遇到了IO操作 print('end') return '返回值' async def fn(): print('fn开始') # 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 task_lis = [ asyncio.create_task(other_tasks()), asyncio.create_task(other_tasks()), ] print('fn结束') # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。 # 此处的await是等待所有协程执行完毕,并将所有协程的返回值保存到done # 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中。 await asyncio.wait(task_lis, timeout=None) response = await asyncio.gather(task_lis[0], task_lis[1]) # 将task_lis作为参数传入gather,等异步任务都结束后返回结果列表 print(f'response :>>>> {response}') def main(): asyncio.run(fn()) if __name__ == '__main__': main() ''' fn开始 fn结束 start start end end response :>>>> ['返回值', '返回值'] '''
【4】aiohtpp对象
-
我们之前学习过爬虫最重要的模块requests,但它是阻塞式的发起请求,每次请求发起后需阻塞等待其返回响应,不能做其他的事情。
-
-
本文要介绍的aiohttp可以理解成是和requests对应Python异步网络请求库,它是基于 asyncio 的异步模块,可用于实现异步爬虫,有点就是更快于 requests 的同步爬虫。
-
安装方式,pip install aiohttp。
-
-
aiohttp是一个为Python提供异步HTTP 客户端/服务端编程,基于asyncio的异步库。
-
-
asyncio可以实现单线程并发IO操作,其实现了TCP、UDP、SSL等协议,
-
aiohttp就是基于asyncio实现的http框架。
-
import aiohttp import asyncio async def main(): async with aiohttp.ClientSession() as session: async with session.get("http://httpbin.org/headers") as response: print(await response.text()) asyncio.run(main())
【五】异步迭代器
【1】什么是异步迭代器
-
实现了 aiter() 和 anext()方法的对象。
-
anext 必须返回一个 awaitable 对象。
-
async for会处理异步迭代器的 anext()方法所返回的可等待对象,直到其引发一个 StopAsyncIteration异常。
【2】什么是异步可迭代对象?
-
可在 async for语句中被使用的对象。
-
必须通过它的 aiter()方法返回一个 asynchronous iterator 。
import asyncio class Reader: """ 自定义异步迭代器(同时也是异步可迭代对象) """ def __init__(self): self.count = 0 async def readline(self): self.count += 1 if self.count == 100: return None return self.count def __aiter__(self): return self async def __anext__(self): val = await self.readline() if val is None: raise StopAsyncIteration return val async def fn(): # 创建异步可迭代对象 async_iter = Reader() # async for 必须放在async def 函数内,否则语法错误。 async for item in async_iter: print(item) asyncio.run((fn()))
【六】异步上下文管理器
-
此种对象通过定义 aenter() 和 aexit() 方法来对 async with 语句中的环境进行控制。
import asyncio class AsyncContextManager: def __init__(self): self.conn = None async def do_something(self): # 异步操作数据库 return 123 async def __aenter__(self): # 异步链接数据库 self.conn = await asyncio.sleep(1) return self async def __aexit__(self, exc_type, exc_val, exc_tb): # 异步关闭数据库链接 await asyncio.sleep(1) async def fn(): async with AsyncContextManager() as f: res = await f.do_something() print(res) asyncio.run(fn())
【七】小结
-
在程序中只要看到 async 和 await 关键字
-
其内部就是基于协程实现的异步编程
-
这种异步编程是通过一个线程在IO等待时间去执行其他任务,从而实现并发。
以上就是异步编程的常见操作,更多内容请参考 Python 官方文档:https://docs.python.org/zh-cn/3.8/library/asyncio.html