with,async,await用法以及协程

with

以前用with时,总是觉着这玩意就是用来管理IO的,后来发现在很多其他地方也用到了,而且和IO操作无关,于是我便认识到我对这个关键字的理解产生的偏差,所以这里重新记录一下我对这几个关键字的学习
举个例子:


class Test:
    def __init__(self, data) -> None:
        self.data = data

    
    def __enter__(self):
        print("现在进入了区域")
        return 123456

    def __exit__(self, *keywords):
        print("现在退出了区域")


with Test(22) as t:
    print("现在在区域中, {}".format(t))
"""
现在进入了区域
现在在区域中, 123456
现在退出了区域
"""
  1. 在使用with后面跟着某个类时,会调用其中的__enter__方法,并且把对应的返回丢给as后面的变量
  2. 在退出with时,会调用__exit__函数

这样我们就能理解为什么他可以管理IO了,我们可以这样来看

from io import TextIOWrapper
class FileManger:
    def __init__(self, *keywords, **args) -> None:
        self.keywords = keywords
        self.args = args
    
    def __enter__(self) -> TextIOWrapper:
        self.file = open(*self.keywords, **self.args)
        return self.file
    
    def __exit__(self, *keywords):
        self.file.close()


with FileManger('./user.txt', encoding = 'utf-8', mode='r') as f:
    print(f.read())

这样我们就实现了文件的自动关闭功能

协程

我们知道在操作系统内,有进程和线程,其中一个进程可以有多个线程,进程内的线程并发的运行。那么协程又是什么?难道是线程的继续的细分么?

其实并不是,协程是一种编程技术,并不是操作系统上的一个运行的实体,来看下面两个伪代码。

def fun1():
	处理数据
	IO请求
	处理数据
def fun2():
	处理数据
	IO请求
	处理数据
fun1()
fun2()

我们知道这是一个串行执行的代码,在fun1进行IO请求时就会发生阻塞,此时主线程就会停在IO请求的代码那里等待IO结束。结束后在处理数据,结束fun1,然后执行fun2。
于是我们就在想,能不能在执行fun1被IO阻塞时,去执行fun2呢?这样就可以最大限度的利用处理器。
于是我们就想到了利用多线程技术,把fun1和fun2都单独的开一个线程然后执行。
但这样的也有问题:

操作系统需要对线程进行调度切换,这会浪费时间

那么我们能不能在一个线程内实现这种技术呢?这是当然可以的,这就是协程。他可以实现代码内部不同代码块异步的执行。

asyncio

Python在3.x很早的版本就开始支持协程了,而asyncio就是一个官方的用于协程的库。而async和await关键字就是协程技术的配套关键字。

我们首先需要了解事件循环,来看下面的伪代码:

任务列表 =  [Task1, Task2, Task3,...]
while True:
	for task in 任务列表:
		if task 已完成:
			删除task
		elif task 可执行 and 非阻塞:
			执行task
	if 任务列表为空
		return

假设我们有多个任务,我们把其放到任务列表中,就会发发生上述的事。

  1. 每个任务都会被按照次序执行
  2. 如果遇到IO等阻塞操作,则会跳过当前的task
  3. 如果完成了,就移出
  4. 如果全部完成就结束

可以把上面的fun1和fun2当成两个task,当执行fun1时可以直接执行,然后IO阻塞就会跳过当前fun1,执行task2。不停地反复循环,直到两个任务都结束。
这样,在fun1被阻塞时,我们就可以腾出时间执行task2了。所以这里有一个关键的数据结构就是这个任务列表,在这个任务列表中的任务会被协程的执行。

async&await

async可以用于加载函数面前,使得这个函数变成一个协程函数(就是可以加到上面的任务列表中)。
而await后面加的是要等待的操作,在加入await后如果发生类似于IO阻塞的情况,此时就会主动地切换到其它的task执行。
举个例子:

import asyncio

async def fun1():
    print("fun1处理数据")
    await asyncio.sleep(1)
    print("fun1处理完成")

async def fun2():
    print("fun2处理数据")
    await asyncio.sleep(1)
    print("fun2处理完成")

task_list = [
    fun1(),
    fun2(),
]

asyncio.run(asyncio.wait(task_list))
"""
fun2处理数据
fun1处理数据
fun2处理完成
fun1处理完成
"""
  1. 给fun1和fun2添加了async关键字,使其变成了协程函数
  2. 使用了asyncio中的sleep函数来模拟IO阻塞
  3. 使用run来建立 任务列表(这里需要注意,任务列表并不是一个简单的列表)并执行。
  4. wait里面存放要执行的函数的列表,用于创建对应的task然后加入 任务列表中

接下来我们来改写一下上面的代码

import asyncio

async def fun1():
    asyncio.create_task(fun2())
    print("fun1处理数据")
    await asyncio.sleep(1)
    print("fun1处理完成")

async def fun2():
    print("fun2处理数据")
    await asyncio.sleep(1)
    print("fun2处理完成")


asyncio.run(fun1())
"""
fun1处理数据
fun2处理数据
fun1处理完成
fun2处理完成
"""

我们来看看内部的细节

  1. asyncio.run(fun1()) 创建了一个任务列表,并且把fun1创建一个task然后加入到任务列表中
  2. 开始执行fun1,遇到asyncio.create_task(fun2()),于是为fun2这个协程函数创建一个task并且加入任务列表中
  3. 开始执行fun1中的数据处理
  4. 遇到IO阻塞,此时就会导致asyncio自动把当前的执行权交给 任务列表中的下一个Task,也就是fun2
  5. fun2处理数据,fun2阻塞,由于此时fun1也还在阻塞,于是就循环的检查他们谁阻塞结束
  6. fun1阻塞先结束,fun1处理完成,fun1退出 任务列表,fun2阻塞结束,fun2吹完成,fun2退出任务列表。携程执行结束

await的进一步使用

等待一个携程函数

来看下面这个代码


import asyncio

async def fun1():
    print("fun1处理数据")
    res = await fun2()
    print(res)
    print("fun1处理完成")

async def fun2():
    print("fun2处理数据")
    await asyncio.sleep(1)
    print("fun2处理完成")
    return "你好啊,fun1"


asyncio.run(fun1())
"""
fun1处理数据
fun2处理数据
fun2处理完成
你好啊,fun1
fun1处理完成
"""

这里的一个特殊之处在于我们await的是一个携程函数,而从运行结果来看,这里使用await的效果等同于直接调用该函数。
也就是这里就相当于是一个顺序的执行我们再加上一些新的改变

import asyncio

async def fun1():
    print("fun1处理数据")
    asyncio.create_task(fun3())
    res = await fun2()
    print(res)
    print("fun1处理完成")

async def fun2():
    print("fun2处理数据")
    await asyncio.sleep(1)
    print("fun2处理完成")
    return "你好啊,fun1"

async def fun3():
    print("fun3处理数据")
    await asyncio.sleep(1)
    print("fun3处理完成")
asyncio.run(fun1())
"""
fun1处理数据
fun2处理数据
fun3处理数据
fun2处理完成
你好啊,fun1
fun1处理完成
fun3处理完成
"""

可以看出这里已经有些复杂了,我们一步一步的来进行解析

  1. 首先fun1加入 任务列表中,然后fun1执行
  2. asyncio.create_task(fun3())使得fun3加入 任务列表中
  3. fun1等待fun2,于是进入fun2中执行
  4. fun2遇到阻塞,而fun1等待fun2,于是fun1和fun2都阻塞(实际上只是阻塞了fun1,因为fun2根本不在任务列表中)
  5. 执行fun3,然后fun3被阻塞,于是循环的判断fun1和fun3
  6. fun1先结束阻塞,继续执行fun2,fun2执行结束后fun1的await内容结束
  7. 接续执行fun1剩下的内容,fun1结束
  8. 接续执行fun3剩下的内容,fun3结束
等待多个携程函数

我们可以这么写

import asyncio

async def fun1():
    print("fun1处理数据")
    done, pending = await asyncio.wait([
        fun2(),
        fun2(),
    ])
    print(done)
    print("fun1处理完成")

async def fun2():
    print("fun2处理数据")
    await asyncio.sleep(1)
    print("fun2处理完成")
    return "你好啊,fun1"

asyncio.run(fun1())
"""
fun1处理数据
fun2处理数据
fun2处理数据
fun2处理完成
fun2处理完成
{<Task finished name='Task-2' coro=<fun2() done, defined at e:/ChatGLM-6B-main/test.py:24> result='你好啊,fun1'>, <Task finished name='Task-3' coro=<fun2() done, defined at e:/ChatGLM-6B-main/test.py:24> result='你好啊,fun1'>}
fun1处理完成
"""

此时可以发现,我们await的是多个携程函数,而返回值也变成了两个值,我们通常只用第一个,里面包含了携程函数的信息和携程函数的返回值

当然也可以这么写,如果不写create_task,会自动的给你创建task的

import asyncio

async def fun1():
    print("fun1处理数据")
    done, pending = await asyncio.wait([
        asyncio.create_task(fun2(), name = 'fun1'),
        asyncio.create_task(fun2(), name = 'fun2'),
    ])
    print(done)
    print("fun1处理完成")

async def fun2():
    print("fun2处理数据")
    await asyncio.sleep(1)
    print("fun2处理完成")
    return "你好啊,fun1"

asyncio.run(fun1())

上述适用create_task手动创建了task,我们以此可以设置更多的参数

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值