协程,也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块相互切换执行
协程一般应用在有IO操作的程序中,因为协程可以利用IO等待的时间去执行一些其他的代码,从而提升代码执行效率。
async
事件循环
事件循环,可以理解为while循环,在周期性的执行一些任务,在任务列表为空时终止循环。
# 伪代码
任务列表 = [ 任务1, 任务2, 任务3,... ]
while True:
可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回
for 就绪任务 in 已准备就绪的任务列表:
执行已就绪的任务
for 已完成的任务 in 已完成的任务列表:
在任务列表中移除 已完成的任务
如果 任务列表 中的任务都已完成,则终止循环
协程函数,定义形式为
async def
的函数。协程对象,调用 协程函数 所返回的对象。
执行协程函数,需要 事件循环 和 协程对象 配合实现。
import asyncio
# 定义一个协程函数
async def func():
print('我是tasks')
# 调用协程函数,返回一个协程对象 注意:函数内部代码不会执行
result = func()
#方式1
# loop = asyncio.get_event_loop() # 创建一个事件循环
# loop.run_until_complete(result) # 将协程当做任务提交到事件循环的任务列表中,协程执行完成之后终止。
# 方式2
asyncio.run(result) # asyncio.run 函数在 Python 3.7 中加入 asyncio 模块
awit
await是一个只能在协程函数中使用的关键字,用于遇到IO操作时挂起 当前协程(任务),当前协程(任务)挂起过程中 事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换回来执行await之后的代码
示例
import asyncio
# await后面可以加 协程对象 task对象 future对象
async def download():
print('start')
await asyncio.sleep(2)
print('end')
return 'ok'
async def func():
print('我是tasks')
res = await download()
print('结束',res)
res2 = await download()
print('结束', res2)
asyncio.run(func())
上述的示例只创建了一个任务,所以无法切换到其他任务
在程序想要创建多个任务对象,需要使用Task对象来实现。
Task对象
Tasks用于并发调度协程,通过asyncio.create_task(协程对象)
的方式创建Task对象,可以让协程加入事件循环中等待被调度执行。
task 添加任务放到事件循环
示例1
import asyncio
async def download():
print('start')
await asyncio.sleep(2)
print('end')
return 'ok'
async def func():
print('main')
# 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。
task1 = asyncio.create_task(download())
task2 = asyncio.create_task(download())
print('end')
ret1 = await task1
ret2 = await task2
print(ret1,ret2)
asyncio.run(func())
示例2
import asyncio
async def download():
print('start')
await asyncio.sleep(2)
print('end')
return 'ok'
# 常规写法
async def func2():
print('main')
task_list = [
asyncio.create_task(download(),name='t1'),
asyncio.create_task(download(),name='t2'),
]
print('end')
done,_ = await asyncio.wait(task_list)
print(done)
asyncio.run(func2())
注意:asyncio.wait
源码内部会对列表中的每个协程执行ensure_future从而封装为Task对象,所以在和wait配合使用时task_list的值为[func(),func()]
也是可以的。
示例3
import asyncio
async def download():
print('start')
await asyncio.sleep(2)
print('end')
return 'ok'
# 放到外部执行
def main():
task_list = [
download(),
download()
]
# 错误:task_list = [ asyncio.create_task(download()), asyncio.create_task(download()) ]
# 此处不能直接 asyncio.create_task,因为将Task立即加入到事件循环的任务列表,
# 但此时事件循环还未创建,所以会报错。
done,_ = asyncio.run(asyncio.wait(task_list))
print(done)
# 使用asyncio.wait将列表封装为一个协程,并调用asyncio.run实现执行两个协程
# asyncio.wait内部会对列表中的每个协程执行ensure_future,封装为Task对象。
main()
asyncio.Future对象
task 继承 funture ,task对象内部await结果的处理基于funture对象来的
import asyncio
async def main():
# 获取当前事件循环
loop = asyncio.get_running_loop()
# # 创建一个任务(Future对象),这个任务什么都不干。
fut = loop.create_future()
# 等待任务最终结果(Future对象),没有结果则会一直等下去。
await fut
asyncio.run(main())
futures.Future对象
在Python的concurrent.futures
模块中也有一个Future对象,这个对象是基于线程池和进程池实现异步操作时使用的对象。
import asyncio
import time
from concurrent.futures.thread import ThreadPoolExecutor
def func():
time.sleep(1)
return 123
def main():
pool = ThreadPoolExecutor(max_workers=5)
fut = pool.submit(func)
print(fut)
main()
两个Future对象是不同的,他们是为不同的应用场景而设计,例如:concurrent.futures.Future
不支持await语法 等。
在Python提供了一个将futures.Future
对象包装成asyncio.Future
对象的函数
两个Future混合使用
import asyncio
import time
from concurrent.futures.thread import ThreadPoolExecutor
def func():
time.sleep(1)
return 123
# 将线程池的future对象转换为async的future对象
async def main2():
loop = asyncio.get_running_loop()
#获取当前的事件循环
#1.为 None 默认生成 ThreadPoolExecutor,任务加入线程池里运行,返回future
fut = loop.run_in_executor(None,func)
result = await fut
print(result)
# 2.
# with ThreadPoolExecutor() as pool:
# fut = await loop.run_in_executor(pool,func)
# print(fut)
asyncio.run(main2())
应用场景
# 异步与非异步模块的案例
import asyncio
import requests
async def download(url):
print('开始下载',url)
loop = asyncio.get_event_loop()
#requests模块不支持异步操作,所以通过线程池配合
fut = loop.run_in_executor(None,requests.get,url)
response = await fut
print('下载完成')
file_name = url.rsplit('/')[-1]+'.jpg'
with open(file_name,'wb') as f:
f.write(response.content)
def main3():
url_list = [
"https://pic.quanjing.com/jp/1w/QJ6144539261.jpg@!350h",
"https://pic.quanjing.com/lv/8f/QJ6625036082.jpg@!350h",
"https://pic.quanjing.com/12/90/QJ8124912494.jpg@!350h"
]
tasks = [download(url) for url in url_list]
asyncio.run(asyncio.wait(tasks))
main3()
# 注:此案例耗费了线程池在等待任务结束,推荐使用aiohttp
异步迭代器
实现了 __aiter__()
和 __anext__()
方法的对象,__anext__
必须返回一个 awaitable 对象
async for
会处理异步迭代器的 __anext__()
方法所返回的可等待对象,直到其引发一个 StopAsyncIteration
异常
示例
import asyncio
class AsyncIterator():
def __init__(self):
self.counter = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.counter >= 10:
raise StopAsyncIteration
self.counter += 1
return self.counter
async def func():
iter = AsyncIterator()
async for i in iter:
print(i)
asyncio.run(func())
异步上下文管理器
定义 __aenter__()
和 __aexit__()
方法来对 async with
语句中的环境进行控制
示例
import asyncio
class AsyncContextManager:
def __init__(self,conn):
self.conn = conn
async def __aenter__(self):
# 异步连接数据库
print('连接')
self.conn = await asyncio.sleep(2) #修改为数据库连接
return self
async def __aexit__(self, exc_type, exc, tb):
# 异步关闭数据库连接
print('关闭')
await asyncio.sleep(2) # 修改为关闭数据库连接
async def do_something(self):
#操作数据库
await asyncio.sleep(2)
return 123
async def func2():
async with AsyncContextManager('conn') as f:
result = await f.do_something()
print(result)
asyncio.run(func2())
这个异步的上下文管理器还是比较有用的,平时在开发过程中 打开、处理、关闭 操作时,就可以用这种方式来处理。
uvloop
uvloop是 asyncio 中的事件循环的替代方案,替换后可以使得asyncio性能提高
示例
#pip3 install uvloop
import asyncio
import uvloop
# uvloop是asyncio的事件循环的代替方案. 事件循环>默认asyncio的事件循环
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# 与之前的一样
#内部的事件循环自动转化为uvloop
asyncio.run(...)
标签:__,异步,协程,编程,print,async,def,asyncio
From: https://www.cnblogs.com/lijun-goods/p/18102373