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

2024-01-08 03:36

本文主要是介绍python协程asyncio的应用,async,await,loop,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关于协程,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列表已经有了东西:

这篇关于python协程asyncio的应用,async,await,loop的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/582200

相关文章

Python 字典 (Dictionary)使用详解

《Python字典(Dictionary)使用详解》字典是python中最重要,最常用的数据结构之一,它提供了高效的键值对存储和查找能力,:本文主要介绍Python字典(Dictionary)... 目录字典1.基本特性2.创建字典3.访问元素4.修改字典5.删除元素6.字典遍历7.字典的高级特性默认字典

Python自动化批量重命名与整理文件系统

《Python自动化批量重命名与整理文件系统》这篇文章主要为大家详细介绍了如何使用Python实现一个强大的文件批量重命名与整理工具,帮助开发者自动化这一繁琐过程,有需要的小伙伴可以了解下... 目录简介环境准备项目功能概述代码详细解析1. 导入必要的库2. 配置参数设置3. 创建日志系统4. 安全文件名处

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

python生成随机唯一id的几种实现方法

《python生成随机唯一id的几种实现方法》在Python中生成随机唯一ID有多种方法,根据不同的需求场景可以选择最适合的方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习... 目录方法 1:使用 UUID 模块(推荐)方法 2:使用 Secrets 模块(安全敏感场景)方法

Redis中Stream详解及应用小结

《Redis中Stream详解及应用小结》RedisStreams是Redis5.0引入的新功能,提供了一种类似于传统消息队列的机制,但具有更高的灵活性和可扩展性,本文给大家介绍Redis中Strea... 目录1. Redis Stream 概述2. Redis Stream 的基本操作2.1. XADD

JSONArray在Java中的应用操作实例

《JSONArray在Java中的应用操作实例》JSONArray是org.json库用于处理JSON数组的类,可将Java对象(Map/List)转换为JSON格式,提供增删改查等操作,适用于前后端... 目录1. jsONArray定义与功能1.1 JSONArray概念阐释1.1.1 什么是JSONA

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

Python办公自动化实战之打造智能邮件发送工具

《Python办公自动化实战之打造智能邮件发送工具》在数字化办公场景中,邮件自动化是提升工作效率的关键技能,本文将演示如何使用Python的smtplib和email库构建一个支持图文混排,多附件,多... 目录前言一、基础配置:搭建邮件发送框架1.1 邮箱服务准备1.2 核心库导入1.3 基础发送函数二、