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提取字典key列表的方法
Jul 11 Python
浅析Python中的多条件排序实现
Jun 07 Python
python Matplotlib画图之调整字体大小的示例
Nov 20 Python
python中将\\uxxxx转换为Unicode字符串的方法
Sep 06 Python
Python图像处理实现两幅图像合成一幅图像的方法【测试可用】
Jan 04 Python
Python While循环语句实例演示及原理解析
Jan 03 Python
python实现实时视频流播放代码实例
Jan 11 Python
Python requests模块session代码实例
Apr 14 Python
python中执行smtplib失败的处理方法
Jul 01 Python
Python命名空间及作用域原理实例解析
Aug 12 Python
python 决策树算法的实现
Oct 09 Python
最新pycharm安装教程
Nov 18 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
如何做到多笔资料的同步
2006/10/09 PHP
用PHP来写记数器(详细介绍)
2006/10/09 PHP
使用php判断服务器是否支持Gzip压缩功能
2013/09/24 PHP
php 获取今日、昨日、上周、本月的起始时间戳和结束时间戳的方法
2013/09/28 PHP
JavaScript CSS 修改学习第四章 透明度设置
2010/02/19 Javascript
jQuery之Deferred对象详解
2014/09/04 Javascript
gridview生成时如何去掉style属性中的border-collapse
2014/09/30 Javascript
JS实现兼容性较好的随屏滚动效果
2015/11/09 Javascript
JQuery遍历元素的后代和同胞实现方法
2016/09/18 Javascript
js原生跨域_用script标签的简单实现
2016/09/24 Javascript
bootstrap+jQuery实现的动态进度条功能示例
2017/05/25 jQuery
Mac 安装 nodejs方法(图文详细步骤)
2017/10/30 NodeJs
基于JavaScript实现五子棋游戏
2020/08/26 Javascript
vue页面切换到滚动页面显示顶部的实例
2018/03/13 Javascript
js中this的指向问题归纳总结
2018/11/28 Javascript
vue实现可视化可拖放的自定义表单的示例代码
2019/03/20 Javascript
微信小程序服务器日期格式化问题
2020/01/07 Javascript
JS数组的常用10种方法详解
2020/05/08 Javascript
Python处理XML格式数据的方法详解
2017/03/21 Python
使用 PyTorch 实现 MLP 并在 MNIST 数据集上验证方式
2020/01/08 Python
python使用pyecharts库画地图数据可视化的实现
2020/03/25 Python
查看jupyter notebook每个单元格运行时间实例
2020/04/22 Python
PyTorch 导数应用的使用教程
2020/08/31 Python
Python unittest如何生成HTMLTestRunner模块
2020/09/08 Python
致铅球运动员加油稿
2014/02/13 职场文书
老人祝寿主持词
2014/03/28 职场文书
理发店策划方案
2014/06/05 职场文书
社区个人对照检查材料(群众路线)
2014/09/26 职场文书
音乐教师个人工作总结
2015/02/06 职场文书
2015年预备党员自我评价
2015/03/04 职场文书
2015年学校党支部工作总结
2015/04/01 职场文书
2015年收银员个人工作总结
2015/04/01 职场文书
小学三年级数学教学反思
2016/02/16 职场文书
22句经典语录:送给优柔寡断和胡思乱想的朋友们
2019/12/13 职场文书
解决Golang time.Parse和time.Format的时区问题
2021/04/29 Golang
Java后端 Dubbo retries 超时重试机制的解决方案
2022/04/14 Java/Android