python asyncio 协程库的使用


Posted in Python onJanuary 21, 2021

asyncio 是 python 力推多年的携程库,与其 线程库 相得益彰,更轻量,并且协程可以访问同一进程中的变量,不需要进程间通信来传递数据,所以使用起来非常顺手。

asyncio 官方文档写的非常简练和有效,半小时内可以学习和测试完,下面为我的一段 HelloWrold,感觉可以更快速的帮你认识 协程 。

定义协程

import asyncio
import time


async def say_after(delay, what):
  await asyncio.sleep(delay)
  print(what)

async 关键字用来声明一个协程函数,这种函数不能直接调用,会抛出异常。正确的调用姿势有:

await 协程()
await asyncio.gather(协程1(), 协程2())
await asyncio.waite([协程1(), 协程2()])
asyncio.create_task(协程())

await 阻塞式调用协程

先来测试前 3 种 await 的方式:

async def main1():
  # 直接 await,顺序执行
  await say_after(2, "2s")
  await say_after(1, "1s")


async def main2():
  # 使用 gather,并发执行
  await asyncio.gather(say_after(2, "2s"), say_after(1, "1s"))


async def main3():
  # 使用 wait,简单等待
  # 3.8 版后已废弃: 如果 aws 中的某个可等待对象为协程,它将自动作为任务加入日程。直接向 wait() 传入协程对象已弃用,因为这会导致 令人迷惑的行为。
  # 3.10 版后移除
  await asyncio.wait([say_after(2, "2s"), say_after(1, "1s")])

python 规定: 调用协程可以用 await,但 await 必须在另一个协程中 —— 这不死循环了?不会的,asyncio 提供了多个能够最初调用协程的入口:

asyncio.get_event_loop().run_until_complete(协程)
asyncio.run(协程)

封装一个计算时间的函数,然后把 2 种方式都试一下:

def runtime(entry, func):
  print("-" * 10 + func.__name__)
  start = time.perf_counter()
  entry(func())
  print("=" * 10 + "{:.5f}".format(time.perf_counter() - start))

print("########### 用 loop 入口协程 ###########")

loop = asyncio.get_event_loop()
runtime(loop.run_until_complete, main1)
runtime(loop.run_until_complete, main2)
runtime(loop.run_until_complete, main3)
loop.close()

print("########### 用 run 入口协程 ###########")

runtime(asyncio.run, main1)
runtime(asyncio.run, main2)
runtime(asyncio.run, main3)

运行结果:

########### 用 loop 入口协程 ###########
----------main1
2s
1s
==========3.00923
----------main2
1s
2s
==========2.00600
----------main3
1s
2s
==========2.00612
########### 用 run 入口协程 ###########
----------main1
2s
1s
==========3.01193
----------main2
1s
2s
==========2.00681
----------main3
1s
2s
==========2.00592

可见,2 种协程入口调用方式差别不大

下面,需要明确 2 个问题:

协程间的并发问题 :除了 main1 耗时 3s 外,其他都是 2s,说明 main1 方式串行执行 2 个协程,其他是并发执行协程。
协程是否阻塞父协程/父进程的问题 :上述测试都使用了 await,即等待协程执行完毕后再继续往下走,所以都是阻塞式的,主进程都在此等待协程的执行完。—— 那么如何才能不阻塞父协程呢? 不加 await 行么? —— 上面 3 种方式都不行!
下面介绍可以不阻塞主协程的方式。

task 实现更灵活的协程

一切都在代码中:

# 验证 task 启动协程是立即执行的
async def main4():
  # create_task() Python 3.7 中被加入
  task1 = asyncio.create_task(say_after(2, "2s"))
  task2 = asyncio.create_task(say_after(1, "1s"))
  # 创建任务后会立即开始执行,后续可以用 await 来等待其完成后再继续,也可以被 cancle
  await task1 # 等待 task1 执行完,其实返回时 2 个task 都已经执行完
  print("--") # 最后才会被打印,因为 2 个task 都已经执行完
  await task2
  # 这里是等待所有 task 结束才继续运行。


# 验证父协程与子协程的关闭关系
async def main5():
  task1 = asyncio.create_task(say_after(2, "2s"))
  task2 = asyncio.create_task(say_after(1, "1s"))
  # 如果不等待,函数会直接 return,main5 协程结束,task1/2 子协程也结束,所以看不到打印
  # 此处等待 1s,则会只看到 1 个,等待 >2s,则会看到 2 个 task 的打印
  await asyncio.sleep(2)


# python3.8 后 python 为 asyncio 的 task 增加了很多功能:
# get/set name、获取正在运行的 task、cancel 功能
# 验证 task 的 cancel() 功能
async def cancel_me(t):
  # 定义一个可处理 CancelledError 的协程
  print("cancel_me(): before sleep")
  try:
    await asyncio.sleep(t)
  except asyncio.CancelledError:
    print("cancel_me(): cancel sleep")
    raise
  finally:
    print("cancel_me(): after sleep")
  return "I hate be canceled"


async def main6():
  async def test(t1, t2):
    task = asyncio.create_task(cancel_me(t1))
    await asyncio.sleep(t2)
    task.cancel() # 会在 task 内引发一个 CancelledError
    try:
      await task
    except asyncio.CancelledError:
      print("main(): cancel_me is cancelled now")
    try:
      print(task.result())
    except asyncio.CancelledError:
      print("main(): cancel_me is cancelled now")

  # 让其运行2s,但在1s时 cancel 它
  await test(2, 1) # await 和 result 时都会引发 CancelledError
  await test(1, 2) # await 和 result 时不会引发,并且 result 会得到函数的返回值

runtime(asyncio.run, main4)
runtime(asyncio.run, main5)
runtime(asyncio.run, main6)

运行结果:

----------main4
1s
2s
--
==========2.00557
----------main5
1s
2s
==========3.00160
----------main6
cancel_me(): before sleep
cancel_me(): cancel sleep
cancel_me(): after sleep
main(): cancel_me is cancelled now
main(): cancel_me is cancelled now
cancel_me(): before sleep
cancel_me(): after sleep
I hate be canceled
==========3.00924

技术总结

细节都在注释里直接描述了,总结一下:

  • await 会阻塞主协程,等待子协程完成
  • await asyncio.gather/wait() 可以实现多个子协程的并发执行
  • await 本身要在协程中执行,即在父协程中执行
  • asyncio.get_event_loop().run_until_complete() 和 asyncio.run() 可作为最初的协程开始入口
  • task 是最新、最推荐的协程方式,可以完成阻塞、非阻塞,
  • task = asyncio.create_task(协程) 后直接开始执行了,并不会等待其他指令
  • await task 是阻塞式,等待 task 执行结束
  • 不 await,非阻塞,但要此时父协程不能退出,否则 task 作为子协程也被退出
  • task 可 cancel() 取消功能,可 result() 获取子协程的返回值

以上就是python asyncio 协程库的使用的详细内容,更多关于python asyncio 协程库的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
跟老齐学Python之复习if语句
Oct 02 Python
python通过urllib2获取带有中文参数url内容的方法
Mar 13 Python
Django查找网站项目根目录和对正则表达式的支持
Jul 15 Python
python嵌套函数使用外部函数变量的方法(Python2和Python3)
Jan 31 Python
深入解析Python编程中super关键字的用法
Jun 24 Python
Python实现的文本对比报告生成工具示例
May 22 Python
使用pandas将numpy中的数组数据保存到csv文件的方法
Jun 14 Python
PyQt5内嵌浏览器注入JavaScript脚本实现自动化操作的代码实例
Feb 13 Python
python 二维矩阵转三维矩阵示例
Nov 30 Python
python 实现二维列表转置
Dec 02 Python
Python selenium 加载并保存QQ群成员,去除其群主、管理员信息的示例代码
May 28 Python
python如何将mat文件转为png
Jul 15 Python
python palywright库基本使用
Jan 21 #Python
python Scrapy爬虫框架的使用
Jan 21 #Python
python 可视化库PyG2Plot的使用
Jan 21 #Python
详解基于Facecognition+Opencv快速搭建人脸识别及跟踪应用
Jan 21 #Python
Python实现石头剪刀布游戏
Jan 20 #Python
python程序实现BTC(比特币)挖矿的完整代码
Jan 20 #Python
python3中celery异步框架简单使用+守护进程方式启动
Jan 20 #Python
You might like
PHP生成静态页
2006/11/25 PHP
php 验证码实例代码
2010/06/01 PHP
Yii2实现ActiveForm ajax提交
2017/05/26 PHP
JavaScript DOM 学习第二章 编辑文本
2010/02/19 Javascript
原生javascript获取元素样式属性值的方法
2010/12/25 Javascript
js中如何把字符串转化为对象、数组示例代码
2013/07/17 Javascript
jQuery切换所有复选框选中状态的方法
2015/07/02 Javascript
js实现商城星星评分的效果
2015/12/29 Javascript
深入浅出ES6新特性之函数默认参数和箭头函数
2016/08/01 Javascript
几句话带你理解JS中的this、闭包、原型链
2016/09/26 Javascript
bootstrap下拉列表与输入框组结合的样式调整
2016/10/08 Javascript
浅谈js控制li标签排序问题 js调用php函数的方法
2016/10/16 Javascript
详解基于javascript实现的苹果系统底部菜单
2016/12/02 Javascript
js 简易版滚动条实例(适用于移动端H5开发)
2017/06/26 Javascript
jQuery实现使用sort方法对json数据排序的方法
2018/04/17 jQuery
Vue CLI3 开启gzip压缩文件的方式
2018/09/30 Javascript
如何封装了一个vue移动端下拉加载下一页数据的组件
2019/01/06 Javascript
详解JavaScript 作用域
2020/07/14 Javascript
[50:04]DOTA2上海特级锦标赛D组小组赛#2 Liquid VS VP第二局
2016/02/28 DOTA
Python中线程编程之threading模块的使用详解
2015/06/23 Python
在Python中执行系统命令的方法示例详解
2017/09/14 Python
python中将一个全部为int的list 转化为str的list方法
2018/04/09 Python
Python操作远程服务器 paramiko模块详细介绍
2019/08/07 Python
python并发编程多进程之守护进程原理解析
2019/08/20 Python
python 怎样进行内存管理
2020/11/10 Python
Python __slots__的使用方法
2020/11/15 Python
原生canvas制作画图小工具的踩坑和爬坑
2020/06/09 HTML / CSS
路易威登和香奈儿手袋:LuxeDH
2017/01/12 全球购物
使用C#编写创建一个线程的代码
2013/01/22 面试题
应届生妇产科护士求职信
2013/10/27 职场文书
《台湾的蝴蝶谷》教学反思
2014/02/20 职场文书
关于国庆节的演讲稿
2014/09/05 职场文书
创先争优承诺书
2015/01/20 职场文书
单位收入证明范本
2015/06/18 职场文书
财产分割协议书
2016/03/22 职场文书
MySQL数据库⾼可⽤HA实现小结
2022/01/22 MySQL