Python中的asyncio代码详解


Posted in Python onJune 10, 2019

asyncio介绍

熟悉c#的同学可能知道,在c#中可以很方便的使用 async 和 await 来实现异步编程,那么在python中应该怎么做呢,其实python也支持异步编程,一般使用 asyncio 这个库,下面介绍下什么是 asyncio :

asyncio 是用来编写 并发 代码的库,使用 async/await 语法。 asyncio 被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等。 asyncio 往往是构建 IO 密集型和高层级 结构化 网络代码的最佳选择。

asyncio中的基本概念

可以看见,使用asyncio库我们也可以在python代码中使用 async 和 await 。在 asyncio 中,有四个基本概念,分别是:

Eventloop

Eventloop 可以说是 asyncio 应用的核心,中央总控, Eventloop 实例提供了注册、取消、执行任务和回调 的方法。 简单来说,就是我们可以把一些异步函数注册到这个事件循环上,事件循环回循环执行这些函数(每次只能执行一个),如果当前正在执行的函数在等待I/O返回,那么事件循环就会暂停它的执行去执行其他函数。当某个函数完成I/O后会恢复,等到下次循环到它的时候就会继续执行。

Coroutine

协程本质就是一个函数,

import asyncio
import time
async def a():
 print('Suspending a')
 await asyncio.sleep(3)
 print('Resuming a')
async def b():
 print('Suspending b')
 await asyncio.sleep(1)
 print('Resuming b')
async def main():
 start = time.perf_counter()
 await asyncio.gather(a(), b())
 print(f'{main.__name__} Cost: {time.perf_counter() - start}')
if __name__ == '__main__':
 asyncio.run(main())

执行上述代码,可以看到类似这样的输出:

Suspending a
Suspending b
Resuming b
Resuming a
main Cost: 3.0023356619999997

关于协程的具体介绍,可以参考我以前的文章python中的协程 不过以前的那种写法,需要使用装饰器,已经过时了。

Future

Future 是表示一个“未来”对象,类似于 javascript 中的 promise ,当异步操作结束后会把最终结果设置到这个 Future 对象上, Future 是对协程的封装。

>>> import asyncio
>>> def fun():
...  print("inner fun")
...  return 111
... 
>>> loop = asyncio.get_event_loop()
>>> future = loop.run_in_executor(None, fun) #这里没有使用await
inner fun
>>> future #可以看到,fun方法状态是pending
<Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/futures.py:348]>
>>> future.done() # 还没有完成
False
>>> [m for m in dir(future) if not m.startswith('_')]
['add_done_callback', 'cancel', 'cancelled', 'done', 'exception', 'get_loop', 'remove_done_callback', 'result', 'set_exception', 'set_result']
>>> future.result() #这个时候如果直接调用result()方法会报错
Traceback (most recent call last):
 File "<input>", line 1, in <module>
asyncio.base_futures.InvalidStateError: Result is not set.
>>> async def runfun():
...  result=await future
...  print(result)
...  
>>>loop.run_until_complete(runfun()) #也可以通过 loop.run_until_complete(future) 来执行,这里只是为了演示await
111
>>> future
<Future finished result=111>
>>> future.done()
True
>>> future.result()
111
Task

Eventloop 除了支持协程,还支持注册 Future 和 Task 2种类型的对象,而 Future 是协程的封装, Future 对象提供了很多任务方法(如完成后的回调,取消,设置任务结果等等),但是一般情况下开发者不需要操作 Future 这种底层对象,而是直接用 Future 的子类 Task 协同的调度协程来实现并发。那么什么是 Task 呢?下面介绍下:

一个与 Future 类似的对象,可运行 Python 协程。非线程安全。 Task 对象被用来在事件循环中运行协程。如果一个协程在等待一个 Future 对象, Task 对象会挂起该协程的执行并等待该 Future 对象完成。当该 Future 对象完成被打包的协程将恢复执行。 事件循环使用协同日程调度: 一个事件循环每次运行一个 Task 对象。而一个 Task 对象会等待一个 Future 对象完成,该事件循环会运行其他 Task 、回调或执行IO操作。

下面看看用法:

>>> async def a():
...  print('Suspending a')
...  await asyncio.sleep(3)
...  print('Resuming a')
...  
>>> task = asyncio.ensure_future(a())
>>> loop.run_until_complete(task)
Suspending a
Resuming a

asyncio中一些常见用法的区别

Asyncio.gather和asyncio.wait

我们在上面的代码中用到过 asyncio.gather ,其实还有另外一种用法是 asyncio.wait ,他们都可以让多个协程并发执行,那么他们有什么区别呢?下面介绍下。

>>> import asyncio
>>> async def a():
...  print('Suspending a')
...  await asyncio.sleep(3)
...  print('Resuming a')
...  return 'A'
... 
... 
... async def b():
...  print('Suspending b')
...  await asyncio.sleep(1)
...  print('Resuming b')
...  return 'B'
... 
>>> async def fun1():
...  return_value_a, return_value_b = await asyncio.gather(a(), b())
...  print(return_value_a,return_value_b)
...  
>>> asyncio.run(fun1())
Suspending a
Suspending b
Resuming b
Resuming a
A B
>>> async def fun2():
...  done,pending=await asyncio.wait([a(),b()])
...  print(done)
...  print(pending)
...  task=list(done)[0]
...  print(task)
...  print(task.result())
...  
>>> asyncio.run(fun2())
Suspending b
Suspending a
Resuming b
Resuming a
{<Task finished coro=<a() done, defined at <input>:1> result='A'>, <Task finished coro=<b() done, defined at <input>:8> result='B'>}
set()
<Task finished coro=<a() done, defined at <input>:1> result='A'>
A

根据上述代码,我们可以看出两者的区别:

asyncio.gather 能收集协程的结果,而且会按照输入协程的顺序保存对应协程的执行结果,而 asyncio.wait 的返回值有两项,第一项是完成的任务列表,第二项表示等待完成的任务列表。

asyncio.wait 支持接受一个参数 return_when ,在默认情况下, asyncio.wait 会等待全部任务完成 (return_when='ALL_COMPLETED') ,它还支持 FIRST_COMPLETED (第一个协程完成就返回)和 FIRST_EXCEPTION (出现第一个异常就返回):

>>> async def fun2():
...  done,pending=await asyncio.wait([a(),b()],return_when=asyncio.tasks.FIRST_COMPLETED)
...  print(done)
...  print(pending)
...  task=list(done)[0]
...  print(task)
...  print(task.result())
...  
>>> asyncio.run(fun2())
Suspending a
Suspending b
Resuming b
{<Task finished coro=<b() done, defined at <input>:8> result='B'>}
{<Task pending coro=<a() running at <input>:3> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10757bf18>()]>>}
<Task finished coro=<b() done, defined at <input>:8> result='B'>
B

一般情况下,用 asyncio.gather 就足够了。

asyncio.create_task和loop.create_task以及asyncio.ensure_future

这三种方法都可以创建 Task ,从Python3.7开始可以统一的使用更高阶的 asyncio.create_task .其实 asyncio.create_task 就是用的 loop.create_task . loop.create_task 接受的参数需要是一个协程,但是 asyncio.ensure_future 除了接受协程,还可以是 Future 对象或者 awaitable 对象:

  1. 如果参数是协程,其底层使用 loop.create_task ,返回 Task 对象
  2. 如果是 Future 对象会直接返回
  3. 如果是一个 awaitable 对象,会 await 这个对象的 __await__ 方法,再执行一次 ensure_future ,最后返回 Task 或者 Future 。

所以 ensure_future 方法主要就是确保这是一个 Future 对象,一般情况下直接用 asyncio.create_task 就可以了。

注册回调和执行同步代码

可以使用 add_done_callback 来添加成功回调:

def callback(future):
 print(f'Result: {future.result()}')
def callback2(future, n):
 print(f'Result: {future.result()}, N: {n}')
async def funa():
 await asyncio.sleep(1)
 return "funa"
async def main():
 task = asyncio.create_task(funa())
 task.add_done_callback(callback)
 await task
 #这样可以为callback传递参数
 task = asyncio.create_task(funa())
 task.add_done_callback(functools.partial(callback2, n=1))
 await task
if __name__ == '__main__':
 asyncio.run(main())

执行同步代码

如果有同步逻辑,想要用 asyncio 来实现并发,那么需要怎么做呢?下面看看:

def a1():
 time.sleep(1)
 return "A"
async def b1():
 await asyncio.sleep(1)
 return "B"
async def main():
 loop = asyncio.get_running_loop()
 await asyncio.gather(loop.run_in_executor(None, a1), b1())
if __name__ == '__main__':
 start = time.perf_counter()
 asyncio.run(main())
 print(f'main method Cost: {time.perf_counter() - start}')
# 输出: main method Cost: 1.0050589740000002

可以使用 run_into_executor 来将同步函数逻辑转化成一个协程,第一个参数是要传递 concurrent.futures.Executor 实例的,传递 None 会选择默认的 executor 。

总结

以上所述是小编给大家介绍的Python中的asyncio代码详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Python 相关文章推荐
Python字符串处理之count()方法的使用
May 18 Python
详解Python编程中包的概念与管理
Oct 16 Python
对pandas replace函数的使用方法小结
May 18 Python
Selenium定位元素操作示例
Aug 10 Python
pyqt5 QProgressBar清空进度条的实例
Jun 21 Python
python读文件的步骤
Oct 08 Python
Python使用pyyaml模块处理yaml数据
Apr 14 Python
python使用hdfs3模块对hdfs进行操作详解
Jun 06 Python
卸载tensorflow-cpu重装tensorflow-gpu操作
Jun 23 Python
keras 两种训练模型方式详解fit和fit_generator(节省内存)
Jul 03 Python
python 基于wx实现音乐播放
Nov 24 Python
Python编程中Python与GIL互斥锁关系作用分析
Sep 15 Python
Django集成CAS单点登录的方法示例
Jun 10 #Python
详解Python中的测试工具
Jun 09 #Python
Python中函数参数匹配模型详解
Jun 09 #Python
Python程序包的构建和发布过程示例详解
Jun 09 #Python
Python面向对象之继承和多态用法分析
Jun 08 #Python
Python基本数据结构之字典类型dict用法分析
Jun 08 #Python
Python学习笔记基本数据结构之序列类型list tuple range用法分析
Jun 08 #Python
You might like
php file_exists 检查文件或目录是否存在的函数
2010/05/10 PHP
PHP自动选择 连接本地还是远程数据库
2010/12/02 PHP
JavaScript 动态生成方法的例子
2009/07/22 Javascript
javascript实现文本域写入字符时限定字数
2014/02/12 Javascript
基于jquery的文字向上跑动类似跑马灯的效果
2014/09/22 Javascript
异步JavaScript编程中的Promise使用方法
2015/07/28 Javascript
Angular2表单自定义验证器的实现
2016/10/19 Javascript
JS根据生日月份和日期计算星座的简单实现方法
2016/11/24 Javascript
vue使用keep-alive实现数据缓存不刷新
2017/10/21 Javascript
浅谈webpack对样式的处理
2018/01/05 Javascript
vue单页应用加百度统计代码(亲测有效)
2018/01/31 Javascript
微信小程序Flex布局用法深入浅出分析
2019/04/25 Javascript
Vue实现开关按钮拖拽效果
2020/09/22 Javascript
[01:28:56]2014 DOTA2华西杯精英邀请赛 5 24 CIS VS DK
2014/05/26 DOTA
Python基于smtplib实现异步发送邮件服务
2015/05/28 Python
Python使用multiprocessing创建进程的方法
2015/06/04 Python
Python将阿拉伯数字转换为罗马数字的方法
2015/07/10 Python
Python简单实现Base64编码和解码的方法
2017/04/29 Python
Python+matplotlib实现填充螺旋实例
2018/01/15 Python
Python系统监控模块psutil功能与经典用法分析
2018/05/24 Python
Django添加sitemap的方法示例
2018/08/06 Python
python命令行参数用法实例分析
2019/06/25 Python
python字典排序的方法
2019/10/12 Python
python打印异常信息的两种实现方式
2019/12/24 Python
Python基于codecs模块实现文件读写案例解析
2020/05/11 Python
HTML5单选框、复选框、下拉菜单、文本域的实现代码
2020/12/01 HTML / CSS
丝芙兰新加坡官网:Sephora新加坡
2018/12/04 全球购物
简述进程的启动、终止的方式以及如何进行进程的查看
2014/02/20 面试题
节约用水倡议书
2014/04/16 职场文书
仓库统计员岗位职责
2015/04/14 职场文书
妈妈再爱我一次观后感
2015/06/08 职场文书
2016年11月份红领巾广播稿
2015/12/21 职场文书
导游词之江苏溱潼古镇
2019/11/27 职场文书
Java 在生活中的 10 大应用
2021/11/02 Java/Android
实战 快速定位MySQL的慢SQL
2022/03/22 MySQL
Android开发实现极为简单的QQ登录页面
2022/04/24 Java/Android