首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >python中的asyncio使用详解与异步协程的处理流程分析

python中的asyncio使用详解与异步协程的处理流程分析

原创
作者头像
霍格沃兹测试开发Muller老师
发布于 2022-08-09 16:38:41
发布于 2022-08-09 16:38:41
1.5K0
举报

一些核心概念

异步函数的定义

普通函数的定义是使用 def 关键词,异步的函数,协程函数(Coroutine)本质上是一个函数,特点是在代码块中可以将执行权交给其他协程,使用async def 来定义

如何调用协程并且得到它的运行结果?

调用普通的函数只需要 result = add2(2),这时函数就可以得到运行,并且将结果4返回给result,如果使用result = add3(2),此时再打印 result 呢?

得到的是一个coroutine对象,<coroutine object add3 at 0x000002ED564A5048>,并不是2+3=5这个结果,怎样才能得到结果呢?

协程函数想要执行需要放到事件循环里执行。

事件循环 Eventloop

Eventloop 是asyncio应用的核心,把一些异步函数注册到这个事件循环上,事件循环会循环执行这些函数,当执行到某个函数时,如果它正在等待I/O返回,如它正在进行网络请求,或者sleep操作,事件循环会暂停它的执行去执行其他的函数;当某个函数完成I/O后会恢复,下次循环到它的时候继续执行。因此,这些异步函数可以协同(Cooperative)运行:这就是事件循环的目标。

返回到上面的函数,想要得到函数执行结果,需要有一个Eventloop

或者使用await 关键字来修饰函数的调用,如result = await add3(2),但是await只能用在协程函数中,所以想要用await关键字就还需要定义一个协程函数

但最终的执行还是需要放到一个事件循环中进行

稍微复杂一点的例子

这段代码定义了两个协程,并将它们放到另外一个协程main函数中,想要获得它们运行的结果,事件循环的特点是当它遇到某个I/O需要等待(如这里的asyncio.sleep()函数)的时候,可以去执行其它的函数,这样,整个函数执行所需要的时间,应该是所有协程中执行时间最长的那个,对于上面这个代码来说,一个sleep了3秒,一个sleep了1秒,总的用时应该是3秒多一点,但结果是这样吗?

它的输出是这样的

它的用时是4秒多一点,而且是先执行了testa函数,然后再执行了testb函数,是串行的依次执行的,并没有像我们想象中的并发执行。那应该怎样才能并发执行呢?

需要将协程放到asyncio.gather() 中运行,上面的代码得到的输出是

可以看到,testa和testb是同步在运行,由于testb只sleep了1秒钟,所以testb先输出了Resuming b,最后将每个协程函数的结果返回,注意,这里是gather()函数里的每一个协程函数都执行完了,它才结果,结果是一个列表,列表里的值顺序和放到gather函数里的协程的顺序是一致的。

除了使用asyncio.gather 来执行协程函数以外,还可以使用Task任务对象

使用asyncio.ensure_future(testa(1))返回一个task对象,此时task进入pending状态,并没有执行,这时print(taska) 得到<Task pending coro=<testa() running at F:/python/python3Test/asynctest.py:7>> 些时,taska.done()返回False,表示它还没有结束,当调用await taska 时表示开始执行该协程,当执行结束以后,taska.done() 返回True,这时可以调用taska.result() 得到函数的返回值,如果协程还没有结束就调用result()方法则会抛个异常,raise InvalidStateError('Result is not ready.')

创建task对象除了使用asyncio.ensure_future()方法还可以使用loop.create_task() 方法

上面一直在使用asyncio.gather()函数来执行协程函数,还有一个asyncio.wait()函数,它的参数是协程的列表。

使用wait和gather有哪些区别呢?

首先,gather是需要所有任务都执行结束,如果某一个协程函数崩溃了,则会抛异常,都不会有结果。

wait可以定义函数返回的时机,

可以是FIRST_COMPLETED(第一个结束的),

FIRST_EXCEPTION(第一个出现异常的),

ALL_COMPLETED(全部执行完,默认的)

这段代码要求在出现第一个异常的时候就结果,函数整体不会崩溃,只是如果这里想要获取结果的话它是一个异常对象。

可以在实际的工作中,由于以前写了太多的多线程与多进程,所以对于以前编写风格和一些由于没有异步支持的库函数来说,由于要写在异步里,所以对于编写代码来说还是要处理很多同步的方法,今天在这里整理一下在异步操作中如果处理同步的函数问题。

为了更好的演示,我准备了三个函数,一个同步的函数,两个异步的函数

协程中控制任务

异步函数的定义

上面的函数,比如说我只想将asyncfunc1() 函数运行并且得结果,可以使用loop.create_task()方法创建一个task对象,task是Futures的子类,当调用loop.run_until_complete() 以后,协程跑完以后,通过task.result()获取协程函数的返回结果。

输出结果为

主线程和跑的协程函数是在同一个线程中。

也可以给task对象添加一个回调方法

输出结果为

loop.run_until_complete 是一个阻塞方法,只有当它里面的协程运行结束以后这个方法才结束,才会运行之后的代码。

其实也可以不调用loop.run_until_complete方法,创建一个task以后,其实就已经在跑协程函数了,只不过当事件循环如果准备开始运行了,此时的task状态是pending,如果不调用事件循环的话,则不会运行协程函数,由于主线程跑完了,子线程也就被销毁了,如代码写成这样:

得到的输出是

所以想要使得协程函数得到执行,需要调用事件循环来执行任务,上面的loop.run_until_complete就是使循环开始跑了,其实也可以使用loop.run_forever(),这个函数就像它的名字一样,会一直跑。只有事件循环跑起来了,那么使用该循环注册的协程才会得到执行,但是如果使用loop.run_forever()则会阻塞在这里,事件循环还有一个stop方法,可以结束循环,我们可以在task对象上添加一个回调方法,当协程执行结束以后,调用事件循环的stop方法来结束整个循环。

除了使用loop.run_until_complete方法,还可以使用asyncio.ensure_future() 方法来运行协程,将上面代码中的task = loop.create_task(asyncfunc1()) 改为 task = asyncio.ensure_future(asyncfunc1())会得到相同的结果,它的参数是协程对象或者futures,也可以传task对象,因为task是futures的子类,当传入的是一个协程对象时,返回一个task对象,传入一个futures的时候,直接返回futures对象,也就是说,在调用asyncio.ensure_future()以后,都会返回一个task对象,都可以为它添加一个回调方法,并且可以调用task.result()方法得到结果(注意如果task没有执行结束就调用result方法,则会抛异常)。

多个协程任务的并行

最上面我准备了两个异步的函数asyncfunc1和asyncfunc2,如果我想要这两个函数同时执行,并且得到它们的返回值该怎么操作呢?

有了上面单协程的经验,我们也可以使用事件循环创建两个task,然后在run_forever()来执行,可以对task添加回调,将结果输出。

输出结果是

此时由于loop调用了run_forever方法,且没有方法调用stop方法,所以程序会一直卡着。

这样是可以将多个协程跑起来,但这样的处理一是繁琐,二是不方便结果的回收。

asyncio有一个gather方法,可以传入多个任务对象,当调用await asyncio.gather(*) 时,它会将结果全部返回。

由于await 只能写在async def 函数中,所以这里还需要新创建一个函数。

两种定义方式都可以,一个是向gather函数传的是协程对象,一个是传的task对象。之后在调用

得到的输出为

这样就达到的协程的并行与结果的回收。

依然是之前准备的三个函数,一个阻塞的,两个异步的。

使用传统的多线程的方式跑同步代码

输出结果

可以看到,主线程和子线程跑在了不同的线程中。

在事件循环中动态的添加同步函数

解决方案是,先启一个子线程,这个线程用来跑事件循环loop,然后动态的将同步函数添加到事件循环中

由于使用ping 命令得到很多输出,所以我对函数稍稍做了修改,只是模拟打印了一行文字,但是函数中的time.sleep(2) 这个是一个阻塞式的函数。

得到的输出为

从输出结果可以看出,loop.call_soon_threadsafe()和主线程不是跑在同一个线程中的,虽然loop.call_soon_threadsafe()没有阻塞主线程的运行,但是由于需要跑的函数ping是阻塞式函数,所以调用了三次,这三次结果是顺序执行的,并没有实现并发。

如果想要实现并发,需要通过run_in_executor 把同步函数在一个执行器里去执行。该方法需要传入三个参数,run_in_executor(self, executor, func, *args) 第一个是执行器,默认可以传入None,如果传入的是None,将使用默认的执行器,一般执行器可以使用线程或者进程执行器。

得到的输出结果

可以看到同步函数实现了并发,但是它们跑在了不同的线程中,这个就和之前传统的使用多线程是一样的了。

上文说到,run_in_executor的第一个参数是执行器,这里执行器是使用concurrent.futures下的两个类,一个是thread一个是process,也就是执行器可以分为线程执行器和进程执行器。它们在初始化的时候都有一个max_workers参数,如果不传则根据系统自身决定。

这里初始化了两个执行器,一个是线程的,一个是进程的,它们执行的效果一样,只是一个跑在了多线程,一个跑在了多进程。

使用concurrent.futures.ThreadPoolExecutor()执行器的结果是

这们的进程ID都是8188,是跑在了同一个进程下。另外注意一下,我这里在初始化的时候传一个max_workers为2,注意看结果的输出,它是先执行了前两个,当有一个执行完了以后再开始执行第三个,而不是三个同时运行的。

使用concurrent.futures.ProcessPoolExecutor()执行器的执行结果

可以看出来它们的进程ID是不同的。

这样看使用run_in_executor和使用多进程和多线程其实意义是一样的。别着急,在讲完异步函数以后就可以看到区别了。

在事件循环中动态的添加异步函数

通过asyncio.run_coroutine_threadsafe 方法来动态的将一个协程绑定到事件循环上,并且不会阻塞主线程

通过asyncio.run_coroutine_threadsafe在loop上绑定了四个协程函数,得到的输出结果为

主线程不会被阻塞,起的四个协程函数几乎同时返回的结果,但是注意,协程所在的线程和主线程不是同一个线程,因为此时事件循环loop是放到了另外的子线程中跑的,所以此时这四个协程放到事件循环的线程中运行的。

注意这里只有run_coroutine_threadsafe方法,没有run_coroutine_thread 方法。

获取协程的返回结果

获取结果可以使用asyncio.gather()方法,这里面传的是coros_or_futures就是协程或者task对象,asyncio.run_coroutine_threadsafe()和run_in_executor()返回的都是Future对象,所以可以将它们共同放到gather里,获取返回值

代码执行结果:

总的时间是取决于所有运行的函数中耗时最长的,这里同步函数有个阻塞的sleep(4) ,所以总的时间是4秒多一点点。

关于在异步协程中的处理流程先总结这么多,之后再学习总结一个与异步相关的各种库如aiohttp的使用等等。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
[494]asyncio与aiohttp使用教程
异步编程是一种非阻塞的编程范式,允许程序在等待I/O操作(如网络请求、文件读写)完成时执行其他任务,而不是干等。
周小董
2025/10/08
2020
Python 异步协程:从 async/await 到 asyncio 再到 async with
在 Python 3.8 以后的版本中,异步编程变得越来越重要。本文将系统介绍 Python 标准库中的异步编程工具,带领大家掌握 async/await 语法和 asyncio 的使用。
Piper破壳
2024/12/23
5730
我实在不懂Python的Asyncio
最近我详细地看了一遍Python的asyncio模块。原因是,我想要使用事件IO来做一些工作,我决定试一下Python世界最近很火的新东东。我最初感受到的是,这个asyncio系统比我预期中的要复杂的多。现在我十分确定的是,我不知道如何正确地使用它。
py3study
2020/01/17
1.6K0
python中重要的模块--asyncio
一直对asyncio这个库比较感兴趣,毕竟这是官网也非常推荐的一个实现高并发的一个模块,python也是在python 3.4中引入了协程的概念。也通过这次整理更加深刻理解这个模块的使用 asyncio 是干什么的? 异步网络操作 并发 协程 python3.0时代,标准库里的异步网络模块:select(非常底层) python3.0时代,第三方异步网络库:Tornado python3.4时代,asyncio:支持TCP,子进程 现在的asyncio,有了很多的模块已经在支持:aiohttp,aiodns
coders
2018/03/30
2.3K0
深入理解Python异步编程
对于其他的并发模型大多数采取的都是线性的方式编写。并且依赖于语言运行时系统或操作系统的底层线程或进程来适当地改变上下文,而基于asyncio的应用要求应用代码显示的处理上下文切换。 asyncio提供的框架以事件循环(event loop)为中心,程序开启一个无限的循环,程序会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
sergiojune
2019/05/07
2.6K0
asyncio模块
async可以定义协程,使用await可以针对耗时操作进行挂起,就与生成器的yield一样,函数交出控制权。协程遇到await,消息循环会挂起该协程,执行别的协程,直到其他协程也会挂起或者执行完毕,在进行下一次执行
星哥玩云
2022/09/08
7620
asyncio模块
协程学习笔记
协程是轻量级线程,拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,即所有局部状态的一个特定组合,每次过程重入时,就相当于进入上一次调用的状态。
somenzz
2020/12/10
7630
协程学习笔记
Python asyncio之协程学习总结
协程(Coroutine)一种电脑程序组件,该程序组件通过允许暂停和恢复任务,为非抢占式多任务生成子程序。协程也可以简单理解为协作的程序,通过协同多任务处理实现并发的函数的变种(一种可以支持中断的函数)。
授客
2023/05/29
1.3K0
Python asyncio之协程学习总结
python多任务—协程(一)
写在前面: 花了一周的时间,对协程做了一个简单的梳理,特别是异步编程asyncio库的使用,做了详细的说明。本文主要包括的知识点有:yield生成器的复习并实现协程的功能、greenlet库实现协程、gevent库实现协程、asyncio异步协程的介绍、异步协程的创建与运行、任务的创建与运行、并发运行gather/wait/as_complete/wait_for等方法的实现、异步协程的嵌套、await关键字的理解等等,这些都是基础。由于篇幅比较长,打算分为两篇,第二篇在介绍一下asyncio的其他用法。
全栈程序员站长
2022/09/14
1.8K0
Python中的异步编程:深入理解和使用asyncio库
asyncio 是 Python 的一个内置库,它的主要用途是编写单线程并发代码,主要通过协程实现。这个库在 Python 3.4 版本中引入,作为 Python 的异步 I/O 框架,提供了基于事件循环的并发模型。
蚂蚁蚂蚁
2024/04/10
7.7K0
Asyncio---Python牛不牛就靠你了
之前在看gevent的时候不小心又看到了这个模块,gevent其实并不是python官方的标准库,有一些缺陷,所以这个时候Asyncio出现了。
我被狗咬了
2019/09/23
1K0
Asyncio---Python牛不牛就靠你了
Python 异步爬虫原理解析及爬取实战
爬虫是 IO 密集型任务,比如我们使用 requests 库来爬取某个站点的话,发出一个请求之后,程序必须要等待网站返回响应之后才能接着运行,而在等待响应的过程中,整个爬虫程序是一直在等待的,实际上没有做任何的事情。
叶庭云
2022/05/08
9580
Python  异步爬虫原理解析及爬取实战
Python协程与异步编程超全总结
协程:又称为微线程,在一个线程中执行,执行函数时可以随时中断,由程序(用户)自身控制,执行效率极高,与多线程比较,没有切换线程的开销和多线程锁机制。
Python编程与实战
2021/02/12
2.1K0
Python协程与异步编程超全总结
python协程初体验
在了解了Python并发编程的多线程和多进程之后,我们来了解一下基于asyncio的异步IO编程 => 协程
测试加
2022/06/21
5100
python协程初体验
python爬虫–协程(初识)
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/100101.html原文链接:
全栈程序员站长
2021/04/19
6070
python爬虫–协程(初识)
Python异步并发机制详解,让你的代码运行效率就像搭上了火箭!!!
Python由于全局锁(GIL)的存在,一直无法发挥多核的优势,其性能一直饱受诟病。 不过,在IO密集型的网络编程各种,异步处理比同步处理能够提升非常之高的速度。 而相对于其他语言,Python还有一个很明显的优势,那就是它的库很多啊!!!
看、未来
2021/09/18
2.4K0
Python协程、异步IO与asyncio
协程是一种轻量级的线程,它允许函数在执行过程中暂停并恢复。与常规函数不同,协程具有多个入口点,可以在函数内部的任何位置暂停和继续执行。Python的协程通过async和await关键字来定义和管理。
五分钟学SRE
2023/11/17
1.2K0
Python协程、异步IO与asyncio
爬虫之异步协程学习总结
协程:英文名(Coroutine),又称为微线程,线程是系统级别的,它们由操作系统调度。而协程则是程序级别的由程序根据需要自己调度。在一个线程中会有很多函数,我们把这些函数称为子程序,在子程序执行过程中可以中断去执行别的子程序,而别的子程序也可以中断回来继续执行之前的子程序,这个过程就称为协程。也就是说在同一线程内一段代码在执行过程中会中断然后跳转执行别的代码,接着在之前中断的地方继续开始执行,类似与yield操作。 通俗易懂的说协程就是通过一个线程来实现代码块(函数)之间的切换执行。 协程函数:函数前面加上async即为协程函数,比如:async def function()。 协程对象:执行协程函数得到的协程对象。执行协程函数创建协程对象,函数内部代码不会执行。
Tommonkey
2023/02/25
9980
Python 协程
协程(Coroutine)又称微线程,即轻量级的线程。协程可以理解成与调用方协作,产出由调用方提供的值的过程。与线程相比,其优势在于上下文切换的成本更低,且由用户自己控制。
CS实验室
2021/03/22
7830
python并发2:使用asyncio处理并发
asyncio 是Python3.4 之后引入的标准库的,这个包使用事件循环驱动的协程实现并发。asyncio 包在引入标准库之前代号 “Tulip”(郁金香),所以在网上搜索资料时,会经常看到这种花的名字。
goodspeed
2020/12/25
2.7K0
相关推荐
[494]asyncio与aiohttp使用教程
更多 >
领券
社区新版编辑器体验调研
诚挚邀请您参与本次调研,分享您的真实使用感受与建议。您的反馈至关重要,感谢您的支持与参与!
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
首页
学习
活动
专区
圈层
工具
MCP广场
首页
学习
活动
专区
圈层
工具
MCP广场