一直不理解python的async
是什么,今天来学习一下。
本质上来说async
还是一个单进程单线程的程序,它类似于event wait。
而它最主要的核心其实就是event loop
,他需要每一个任务主动告诉event loop
,我结束了,你可以切换下一个任务了,所以也不会有竞争关系。
1.coroutine
想要了解async,肯定要了解coroutine
。
如果你是async def function
,他其实就是async coroutine
,它定义了一个coroutine的过程。
如果你调用它,他返回的是coroutine object
,切换到event loop
控制一切。
async def test():
print("你好")
test()
:4: RuntimeWarning: coroutine 'test' was never awaited
test()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
可以看到他发送了一个coroutine object,但他并不是运行方法,那我们怎么执行这个async方法
呢。
我们首先知道,他现在以及交接到event loop
控制整个方法的状态了。我们把普通方法称作synchronize
,而切换到asynchronize
方法的方式就是添加async
,在方法体前面。
而运行asynchronize
方法,可以使用asyncio.run(coroutine)
来实现,他的参数是放入一个coroutine
,在他运行后,他会先建立event loop
,event loop
会将coroutine
变成一个task
进行执行。
import asyncio
async def test():
print("你好")
asyncio.run(test())
这里光有一个task,根本体现不出asynchronize
的强大性。
那我们如何创建多个task,并让他们依次执行呢?
2. Task
一个coroutine
无法执行,除非被包裹成task
,而await
可以做到将一个coroutine function
包装成task,并且告诉了event loop
我这有了一个新的task,并且当前这个task必须等到await这个task执行完才可以继续。
import asyncio
async def hi(hi):
print(f"你好,我是{hi}")
async def test():
await hi(1)
await hi(2)
print("你好")
asyncio.run(test())
下图就是结果
如果方法有返回,使用普通对象进行保存即可。
我们要知道event loop
并不能抢夺运行权,必须task主动完成交回。所以,task如果卡死,那么event loop
也就卡死了。
那么,我们如何在进行第一个方法进行等待的时候,我们也等待第二个方法的等待,让他们同时运行呢,这才是我们协程的意义啊。
因为await要做的事情太多了,如果我们可以把await做的事情分出来一部分的话,就会好很多。
import asyncio
async def hi(hi):
print(f"你好,我是{hi}")
async def test():
task1 = asyncio.create_task(hi(1))
task2 = asyncio.create_task(hi(2))
await task1
await task2
print("你好")
asyncio.run(test())
那我们这些操作,一次次创建task实在是太麻烦,所以asyncio
为我们封装了gather方法,他里面可以放入task或者coroutine,gather会将coroutine变成task。如果前面再加上await就可以执行这些task。
3. 异步编程
3.1 事件循环
理解成一个死循环,不停地监测并且执行任务。
while True:
for 就绪任务 in 就绪任务列表:
执行
for 已完成任务 in 完成任务列表:
删除
# 如果内容都已经执行完,则关闭循环
event_loop=asyncio.get_event_loop();
event_loop.run_until_complete( asyncio.wait(tasks) )
3.2 快速上手
方法里面有await,这个方法就停在这里,而event_loop去执行其他的task,等到他的await完成,在跳回这个task。
import asyncio
import requests
async def download_image_io(img):
res = requests.get(img)
img_name = img[img.rfind("/") + 1:]
with open(img_name, 'wb') as file:
file.write(res.content)
await asyncio.sleep(1)
print(f"下载路径为{img}的图片下载完成")
async def download_image(img_list):
img = img_list[0]
print(f"开始下载路径为{img}的图片")
await download_image_io(img)
async def fun1():
print("执行方法1")
await asyncio.sleep(3)
print("结束方法1")
async def fun2():
print("执行方法2")
await asyncio.sleep(1)
print("结束方法2")
if __name__ == '__main__':
img_list = [
'https://proxy.pixivel.moe/c/540x540_70/img-master/img/2020/04/07/18/25/11/80628691_p0_master1200.jpg',
'https://proxy.pixivel.moe/c/540x540_70/img-master/img/2020/02/13/07/38/01/79452182_p0_master1200.jpg',
'https://proxy.pixivel.moe/c/540x540_70/img-master/img/2020/01/25/03/28/40/79079554_p0_master1200.jpg'
]
tasks = asyncio.gather(download_image(img_list), fun1(), fun2())
event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(tasks)
假如我们需要在方法的等待中添加多个方法,我们总不能一直create_task
,asyncio
为我们提供了wait方法,里面可以直接放入多个coroutine的列表,wait会返回tasks对象,给予await进行运行即可。
async def download_image_io(img):
res = requests.get(img)
img_name = img[img.rfind("/") + 1:]
with open(img_name, 'wb') as file:
file.write(res.content)
await asyncio.wait([sleep(), sleep()])
print(f"下载路径为{img}的图片下载完成")
async def sleep():
await asyncio.sleep(1)
接受多个返回值需要使用对象done
3.3 Future对象
Task继承Future,Task的await处理内部是Future执行的。
如果没有获得future的内容,他将一直等待
async def download_image(img_list):
loop = asyncio.get_running_loop()
future = loop.create_future()
# 如果没有下面这一句,他没有返回值,
#下面的wait就可能一直等待。
future.set_result(123)
data = await future
print(data)
await download_image_io(img)
对于task他会绑定协程,而Future并没有绑定任何,所以如果什么都不给,await并不知道他是否完成
3.4 concurrent.futures.Future对象
使用进程池、线程池实现异步操作。
def fun(value):
time.sleep(1)
print(value)
#主要创建线程池
pool = ThreadPoolExecutor(max_workers=5)
for i in range(10):
#pool.submit
val = pool.submit(fun1, i)
print(val)
我们通常要异步与线程池混合使用,因为一些api并不支持异步操作,那我们就需要这些池子的帮助了。
async def download_image(img, name):
event_loop = asyncio.get_event_loop()
future = event_loop.run_in_executor(None, requests.get, img)
res = await future
with open(name, "wb+") as file:
file.write(res.content)
tasks = []
for img in img_list:
print(f"开始下载{img}张图片")
img_name = img[img.rfind("/") + 1:]
tasks.append(download_image(img, img_name))
print(f"下载完成{img}")
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
4. 异步上下管理器
这种对象通过私立方法 __aenter__
和 __aexit__
方法来对async with
语句中的环境进行配置。
class Resource:
def __init__(self):
return
async def do_something(self):
# 异步数据库操作
return 666
async def __aenter__(self):
#异步连接数据库
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
#异步退出数据库
await asyncio.sleep(1)
return
async def c():
async with Resource as res:
something_ = await res.do_something()
print(something_)
if __name__ == '__main__':
asyncio.run(c())
5 uvloop
是asyncio的事件循环的替代方案。事件循环>默认asyncio的事件循环。
pip install uvloop
#将协议转换为uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
注意:一个asgi
->uvicorn
内部使用的就是uvloop
6. 实战
6.1 redis
在使用redis的时候,需要操作实际上都是io,这时候我们可以让线程干一些自己的事情。
pip3 install aioredis
import asyncio
from concurrent.futures import ThreadPoolExecutor
import aioredis
import requests
async def main():
# Redis client bound to single connection (no auto reconnection).
redis = aioredis.from_url(
"redis://localhost", encoding="utf-8", decode_responses=True
)
async with redis.client() as conn:
await conn.set("my-key", "value")
val = await conn.get("my-key")
print(val)
async def redis_pool():
# Redis client bound to pool of connections (auto-reconnecting).
redis = aioredis.from_url(
"redis://localhost", encoding="utf-8", decode_responses=True
)
await redis.set("my-key", "value")
val = await redis.get("my-key")
print(val)
6.2 异步MySql
pip install aiomysql
import asyncio
import aiomysql
async def execute():
print("开始查询blog。。。。")
conn = await aiomysql.connect(host='localhost', port=3306, user='root', password='root', db='blog')
cursor = await conn.cursor()
await cursor.execute('SELECT * FROM blog;')
result = await cursor.fetchall()
print(result)
await cursor.close()
conn.close()
async def exe():
print("开始查询fruit表。。。。")
conn = await aiomysql.connect(host='localhost', port=3306, user='root', password='root', db='fruit')
cursor = await conn.cursor()
await cursor.execute('SELECT * FROM fruit;')
result = await cursor.fetchall()
print(type(result))
await cursor.close()
conn.close()
asyncio.run(asyncio.wait([execute(),exe()]))
6.3 爬虫
使用aiohttp
,来进行爬虫异步。
pip install aiohttp
aiohttp
具体怎么使用,请查看官方文档。