python的async

一直不理解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_taskasyncio为我们提供了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具体怎么使用,请查看官方文档。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值