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中logging模块用法示例总结

《Python中logging模块用法示例总结》在Python中logging模块是一个强大的日志记录工具,它允许用户将程序运行期间产生的日志信息输出到控制台或者写入到文件中,:本文主要介绍Pyt... 目录前言一. 基本使用1. 五种日志等级2.  设置报告等级3. 自定义格式4. C语言风格的格式化方法

Python实现精确小数计算的完全指南

《Python实现精确小数计算的完全指南》在金融计算、科学实验和工程领域,浮点数精度问题一直是开发者面临的重大挑战,本文将深入解析Python精确小数计算技术体系,感兴趣的小伙伴可以了解一下... 目录引言:小数精度问题的核心挑战一、浮点数精度问题分析1.1 浮点数精度陷阱1.2 浮点数误差来源二、基础解决

使用Python实现Word文档的自动化对比方案

《使用Python实现Word文档的自动化对比方案》我们经常需要比较两个Word文档的版本差异,无论是合同修订、论文修改还是代码文档更新,人工比对不仅效率低下,还容易遗漏关键改动,下面通过一个实际案例... 目录引言一、使用python-docx库解析文档结构二、使用difflib进行差异比对三、高级对比方

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达

从入门到精通详解Python虚拟环境完全指南

《从入门到精通详解Python虚拟环境完全指南》Python虚拟环境是一个独立的Python运行环境,它允许你为不同的项目创建隔离的Python环境,下面小编就来和大家详细介绍一下吧... 目录什么是python虚拟环境一、使用venv创建和管理虚拟环境1.1 创建虚拟环境1.2 激活虚拟环境1.3 验证虚

详解python pycharm与cmd中制表符不一样

《详解pythonpycharm与cmd中制表符不一样》本文主要介绍了pythonpycharm与cmd中制表符不一样,这个问题通常是因为PyCharm和命令行(CMD)使用的制表符(tab)的宽... 这个问题通常是因为PyCharm和命令行(CMD)使用的制表符(tab)的宽度不同导致的。在PyChar

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

Python中Json和其他类型相互转换的实现示例

《Python中Json和其他类型相互转换的实现示例》本文介绍了在Python中使用json模块实现json数据与dict、object之间的高效转换,包括loads(),load(),dumps()... 项目中经常会用到json格式转为object对象、dict字典格式等。在此做个记录,方便后续用到该方

从基础到高级详解Python数值格式化输出的完全指南

《从基础到高级详解Python数值格式化输出的完全指南》在数据分析、金融计算和科学报告领域,数值格式化是提升可读性和专业性的关键技术,本文将深入解析Python中数值格式化输出的相关方法,感兴趣的小伙... 目录引言:数值格式化的核心价值一、基础格式化方法1.1 三种核心格式化方式对比1.2 基础格式化示例

Python与MySQL实现数据库实时同步的详细步骤

《Python与MySQL实现数据库实时同步的详细步骤》在日常开发中,数据同步是一项常见的需求,本篇文章将使用Python和MySQL来实现数据库实时同步,我们将围绕数据变更捕获、数据处理和数据写入这... 目录前言摘要概述:数据同步方案1. 基本思路2. mysql Binlog 简介实现步骤与代码示例1