python协程asyncio的应用,async,await,loop

news/2025/2/11 8:44:39/

关于协程,asyncio,async,await,loop的概念,参照上一篇文章可迭代对象,迭代器,生成器,协程-CSDN博客

上一章我们详细的讲解了上述各个名词的概念,但是这些东西实际上该怎么用还不太清除,这一章做一点补充;

总结: 

1:async用于定义协程函数

2:协程函数初始化得到的是协程对象,协程对象不能直接执行;

3:协程对象的执行方式,要么用await执行,但是await只能出现在协程函数里面,所以它不能作为协程的启动方式,要么把协程转化为Task运行,因为Task里面含有send(类似于生成器的next,这里用next其实也行,兼容),同理,await为什么能运行协程也是因为它含有send,因为await就是yield from的平替;

4:有了Task以后,如果直接运行Task.run就是手动驱动协程函数,那么对于同时要运行多个协程,就要手动运行多个Task.run,很费劲,所以引入eventloop,eventloop有两个记录列表,一个是就绪列表_ready(一个双向列表),一个是待定任务列表_schedule(一个小顶堆,保证最小时刻任务在最前面);此时loop用run_forver的无限循环,不停的访问_ready去运行任务,并不停的将待定任务列表中满足条件的任务加入到就绪列表;待任务全部运行完则退出;run_until_complete其实就是run_forever的进一步包装

5:asyncio.wait()和asyncio.gather()主要功能就是把各个协程包装成Task,当然网上说他俩都会运行协程函数拿到结果,但其实不是他俩主动驱动协程函数的,本质也是通过loop来驱动执行的,只不过两者做了一定的包装,因为loop.run_until_complete这些只接受一个变量参数,你要运行多个协程的时候终归要先统一起来,asyncio.wait()本身也是一个协程,asyncio.gather()不是协程,但好像也不是普通函数,它继承自future,暂定为一个future吧;

6:asyncio.run()其实是对loop.run_until_complete()的包装,也就是说asyncio.run(协程a)等同于run_until_complete(协程a)

那么整个协程的工作原理即,每次loop运行一个协程函数A,如果在这个协程函数A里面遇到await,则需要等待该await后面跟的协程函数B执行完成,但是B的运行可能需要满足一定的条件,比如B是一个IO,需要等待输入之类的,所以A就被loop挂起,让出当前线程的执行权,loop转而去执行其他协程函数比如C,以此实现单线程多任务并发;

 一:async用于定义协程函数

也就是说,在函数定义前面加上async,则讲该函数变成了一个协程coroutine,此时你是无法直接运行该函数的,因为通过上一章我们知道此时的函数有点类似于定义了yield的函数,定义了yield的函数你第一次初始化的时候也是不运行的,而是返回一个generator生成器,要运行需要用next()和或者send(),那么coroutine协程本身也是从yield发展过来的,所以它也不能直接运行,但此时不再是用next和send了,虽然本质上没啥区别,这里要运行协程函数可以用asyncio.run(协程函数名);

二:Task和Future

上一章我们把本来的三个普通函数全部变成了协程函数async,但其实除此之外,还添加了两个小组件,一个叫做Future(上一章图里最下方的小橙方块),一个叫做Task(继承自Future,上一章图里最上方的小蓝方块),Task的作用就是调控整个协程函数,那么对应的我们要运行协程函数,则必须创建Task任务,因为Task函数里面才有send函数,如图:

这和上一章里面构建的Task样例几乎一样,只不过样例是极简的,并且样例里面设置的是run函数,这里是_step函数; 

也就是说,所有的协程函数,必须转化为Task才可以运行(除了用await方式运行的协程,因为await,相当于yield from,它自己带有send功能),那么我们这里总结一下有多少种运行方式:

a:await

最常用的await关键字可以直接运行协程函数,但是await关键字只能在协程函数里面实现,所以它不能作为协程启动的方式;

b:asyncio.run(协程函数名)

上面的asyncio.run(协程函数名),其实run里面也做了转换操作,把协程转成了Task,asyncio.run里面实际上是调用的loop.run_until_complete来执行函数,如图:

再深入进去, run_until_complete里面做了转换操作,Task继承自Future,这里就将协程转化为了Task:

c: asyncio.wait()

参考自:剖析asyncio.wait的使用_笔记大全_设计学院 (python100.com)

asyncio.wait函数的作用是等待一组协程(coroutine)。当一组协程全部完成后,wait()方法返回两个Set对象:一个是完成的任务列表,一个是未完成的任务列表。

wait()方法常用的参数包括:

1. coros:协程对象的集合。

2. loop=None:事件主循环对象,若未指定默认为asyncio.get_event_loop()。

wait()方法返回的是一个协程

来看一下wait方法的源码:

 

可以看到这里也是把传给它的协程转化为了Task;

但有一点需要注意,asyncio.wait()本身就是一个协程函数,所以它没办法直接asyncio.wait([协程1, 协程2])这样运行的,他一般需要跟在await关键字后面,或者给到loop.run_until_complete等eventloop里面执行

d: asyncio.gather()

其实和asyncio.wait()非常相似,也是运行一组协程函数;但不同点在于

(1):asyncio.wait()用一个set保存所有协程函数,因为set是无序的,所以写成函数也是无序执行的;而gather是顺序执行,顺序输出的;

(2):asyncio.wait()会返回两个值:done 和 pending,done 为已完成的协程 Task,pending 为超时未完成的协程 Task,需通过 future.result 调用 Task 的 result;而asyncio.gather 返回的是所有已完成 Task 的 result,不需要再进行调用或其他操作,就可以得到全部结果;此外,wait()可以在任何一个协程完成时进行一些处理的场景,因为它可以设置任何一个完成时返回:

参考:asyncio.gather vs asyncio.wait_asyncio gather 为什么要加*-CSDN博客

(3):gather不是协程函数,但具体是啥也不清楚,显示如下:

这个类继承自future,姑且当作是一个future吧; gather函数虽然不是协程函数,但仍然无法直接执行,目前来看能驱动它的只有await,或者loop.run_until_complete等eventloop里面驱动;

3:loop

回忆上一章的内容,我们最开始是直接用Task来执行协程的,是手动进行驱动,后面我们引入了eventloop,让他来全程调度协程(其实就是一个无限循环,循环里面不停的运行各个回调函数,这里的回调函数可以是协程函数,也可以是普通函数);

回忆一下eventloop的作用:它有两个列表,一个是就绪列表_ready,一个是待定列表_scheduled,然后每次调用loop.run_forever,或者loop.call_soon,或者loop.call_later来不断执行_ready里面的就绪任务,并且不断判断待定列表里面的任务是否已经就绪,如果就绪,就加入到就绪任务列表执行;所以我们现在准备说的loop.run_until_complete,其实和上面的run_forever,call_soon,call_later这些是一样的,就是用来执行_ready任务列表的,甚至其实run_until_complete就是对run_forever的进一步包装;

所以上面的asyncio.wait()也好,或者asyncio.gather也好,都可以丢给loop来自动调配,也就实现了多任务协作的功能;

我们拿loop.run_until_complete来举例子吧:

loop.run_until_complete可以直接接协程函数,也可以接asyncio.wait()或者asyncio.gather这些,因为本质上loop.run_until_complete里面会把输入强制转化为Task(对于普通函数的话,可能是添加__await__方法,个人理解):

然后就是我们上一章讲过的死循环run_forever和单词循环运行_ready里面准备好的函数:

 

run_once里面的逻辑和上一章我们的极简版本是一样的:

先判断当前已经就绪的任务有多少,然后一个个pop出来,由于这里的任务已经转化为了Task,所以默认调用Task的run方法;整体来看,还是非常清晰明了的;

但是这里存在一个bug,就是我们没看见把任务加入到_ready就绪列表里面的步骤,所以我又回过头去调试看了一下,发现了一点新的亮点:

 asyncio.wait和asyncio.gather进一步的区别:

由于asyncio.wait本质是一个协程函数,所以

loop.run_until_complete(asyncio.wait([ceshi(), ceshi2()]))

这段代码里面asyncio.wait([ceshi(), ceshi2()])返回一个协程对象,并不会先执行asyncio.wait,而是直接执行loop.run_until_complete,在loop.run_until_complete里面把这个协程对象转化为Task的时候,也把这个任务加入到了_ready就绪任务里面:

此处进去后是loop.create_task:

再进去:

 在这里,把协程转化为了Task,并且加入到了_ready就绪列表;然后进入run_forever执行;

但是asyncio.gather不是协程函数,所以,loop.run_until_complete(asyncio.gather(ceshi(), ceshi2()))其实是先进入asyncio.gather函数,再进入run_until_complete,而且gather函数里面就把协程对象转化为了Task,并加入到了loop的_ready就绪列表,调试如下(每一层往下):

 

在这个_ensure_future里面,第一次的时候,没有传入loop,所以先调用了events._get_event_loop(stacklevel=4)去取现有的loop,也就是再最上层我们初始化的loop: 

所以这里用loop.create_task把协程对象转化为Task的时候,就已经加入到_ready就绪列表了 ,后续我们进入到run_until_complete的时候可以发现,此时它的_ready列表已经有了东西:


http://www.ppmy.cn/news/1295004.html

相关文章

Hexo+GitHub搭建个人网站简要流程及遇到的问题

文章目录 一、必要准备1.1 安装配置git和新建GitHub仓库1.2 安装配置Node和Hexo 二、将页面部署到GitHub三、发布博客四、生成永久链接五、更换主题六、配置主题遇到的很多问题 一、必要准备 1.1 安装配置git和新建GitHub仓库 git的安装和配置没什么特别的。 在GitHub中新建…

基于Redis + Lua脚本语言 + 注解:构建高效的请求接口限流方案

为什么接口限流 黑客疯狂请求系统接口的某一个接口 而且每次都需要数据库io操作 。如果并发量很大。导致的结果就是 宕机。 解决方案很多 今天我们就先来基于Redis Lua脚本语言 注解:构建高效的请求接口限流方案 限流效果 ~~~~连续点击 源码地址在最下面 lua安装…

el-upload上传文件

需求:选中或拖拽文件后,使用http-request属性实现自动上传,并根据后端传回来的结果显示错误和控制fileList的显示,如果后端返回成功,则文件显示在文件列表处,如果后端返回失败,则文件列表不显示…

分割系统日志,解决syslog文件过大的问题

问题描述 随着系统使用时间越长,系统日志/var/log/syslog也会越来越大,达到几g或十几g,不但占用硬盘空间,且拷贝日志进行分析时也需要较长时间。 解决方案 对系统日志syslog进行分割,减少系统日志的大小 执行如下脚本文…

【数据仓库与联机分析处理】多维数据模型

目录 一、数据立方体 二、数据模型 (一)星形模型 (二)雪花模式 (三)事实星座模式 三、多维数据模型中的OLAP操作 (一)下钻 (二)上卷 (三…

Nginx 常用变量 与 防盗链

目录 1.常用变量 2. $http_referer 配置防盗链 2.1 referer 2.2 配置防盗链 1.常用变量 变量说明 $args 请求中的参数,也叫查询参数 $content_length HTTP响应信息里的"Content-Length" $document_root nginx虚拟主机配置文件中的root站点根目录…

【Oracle】查看表空间使用率sql

10G进asm 直接su - oracle 然后asmcmd 11G进asm,需要su - grid 然后asmcmd 1.切换到相关oracle数据库实例下: echo $ORACLE_SID 查看当前数据库实例 或者进入sql下执行 select instance_name from V$instance; export ORACLE_SID=sohdb1 切换到sohdb1数据库实例下…

精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(上)

攻破Java技术盲点之剖析动态代理的实现原理和开发指南 背景介绍静态代理和动态代理动态代理与静态代理的区别 进入正题重温:静态代理实现静态代理案例静态代理的弊端 重温:动态代理Java动态代理InvocationHandlerJava动态代理的实现下面看具体的代码实例…