前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python asyncio 异步 I/O - 协程(Coroutine)与运行

python asyncio 异步 I/O - 协程(Coroutine)与运行

作者头像
上海-悠悠
发布2022-03-03 15:40:16
1.5K0
发布2022-03-03 15:40:16
举报
文章被收录于专栏:从零开始学自动化测试

前言

Python 在 3.5 版本中引入了关于协程的语法糖 async 和 await, 在 python3.7 版本可以通过 asyncio.run() 运行一个协程。 所以建议大家学习协程的时候使用 python3.7+ 版本,本文示例代码在 python3.8 上运行的。

协程 coroutines

协程(coroutines)通过 async/await 语法进行声明,是编写 asyncio 应用的推荐方式。 例如,以下代码段(需要 Python 3.7+)

代码语言:javascript
复制
import asyncio
import time

async def fun():
    print(f'hello start: {time.time()}')
    await asyncio.sleep(3)
    print(f'------hello end : {time.time()} ----')

# 运行
print(fun())

当我们直接使用fun() 执行的时候,运行结果是一个协程对象coroutine object,并且会出现警告

代码语言:javascript
复制
 RuntimeWarning: coroutine 'fun' was never awaited
  print(fun())
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

在函数前面加了async,这就是一个协程了,运行的时候需使用asyncio.run()来执行(需要 Python 3.7+)

代码语言:javascript
复制
import asyncio
import time

async def fun():
    print(f'hello start: {time.time()}')
    await asyncio.sleep(3)
    print(f'------hello end : {time.time()} ----')

# 运行
asyncio.run(fun())

运行结果

代码语言:javascript
复制
hello start: 1646009849.5220373
------hello end : 1646009852.5258074 ----

协程运行三种机制

要真正运行一个协程,asyncio 提供了三种主要机制:

  • asyncio.run() 函数用来运行最高层级的入口点 “fun()” 函数 (参见上面的示例。 )
  • 等待一个协程。 如: await asyncio.sleep(3)
  • asyncio.create_task() 函数用来并发运行作为 asyncio 任务 的多个协程。

通过前面第一个示例,知道了asyncio.run()来运行一个协程,接着看 await 等待的使用

代码语言:javascript
复制
import asyncio
import time

async def fun_a():
    print(f'hello start: {time.time()}')
    await asyncio.sleep(3)
    print(f'------hello end : {time.time()} ----')

async def fun_b():
    print(f"world start: {time.time()}")
    await asyncio.sleep(2)
    print(f'------world end : {time.time()} ----')

async def main():
    print('start main:')
    await fun_a()
    await fun_b()
    print('-----------end start----------')

asyncio.run(main())

运行结果

代码语言:javascript
复制
start main:
hello start: 1646010206.405429
------hello end : 1646010209.4092102 ----
world start: 1646010209.4092102
------world end : 1646010211.4115622 ----
-----------end start----------

运行的入口是main(), 遇到await 会先去执行 fun_a(),执行完成后再去执行fun_b()。

需注意的是,await 后面不能是普通函数,必须是一个可等待对象(awaitable object),Python 协程属于 可等待 对象,因此可以在其他协程中被等待。 如果一个对象能够被用在 await表达式中,那么我们称这个对象是可等待对象(awaitable object)。很多asyncio API都被设计成了可等待的。 主要有三类可等待对象:

  • 协程coroutine
  • 任务Task
  • 未来对象Future。

在前面这个示例中,fun_a() 和 fun_b()是按顺序执行的,这跟我们之前写的函数执行是一样的,看起来没啥差别,接着看如何并发执行2个协程任务 asyncio.create_task() 函数用来并发运行作为 asyncio 任务的多个协程

代码语言:javascript
复制
import asyncio
import time

async def fun_a():
    print(f'hello start: {time.time()}')
    await asyncio.sleep(3)
    print(f'------hello end : {time.time()} ----')

async def fun_b():
    print(f"world start: {time.time()}")
    await asyncio.sleep(2)
    print(f'------world end : {time.time()} ----')

async def main():
    print('start main:')
    task1 = asyncio.create_task(fun_a())
    task2 = asyncio.create_task(fun_b())
    await task1
    await task2
    print('-----------end start----------')

asyncio.run(main())

运行结果

代码语言:javascript
复制
start main:
hello start: 1646010554.0892649
world start: 1646010554.0892649
------world end : 1646010556.108237 ----
------hello end : 1646010557.08811 ----
-----------end start----------

从运行的结果可以看到,hello start 和 world start 的开启时间是一样的,也就是2个任务是并发执行的。

并发任务的误区

当我们知道协程可以实现并发后,于是小伙伴就想小试一下,去模拟并发下载图片,或者去并发访问网站。 先看第一个误区: 把上一个示例中的 await asyncio.sleep(3) 换成 time.sleep(3),假设是完成任务需花费的时间。

代码语言:javascript
复制
import asyncio
import time

async def fun_a():
    print(f'hello start: {time.time()}')
    time.sleep(3)  # 假设是执行请求花费的时间
    print(f'------hello end : {time.time()} ----')

async def fun_b():
    print(f"world start: {time.time()}")
    time.sleep(2)  # 假设是执行请求花费的时间
    print(f'------world end : {time.time()} ----')

async def main():
    print('start main:')
    task1 = asyncio.create_task(fun_a())
    task2 = asyncio.create_task(fun_b())
    await task1
    await task2
    print('-----------end start----------')

asyncio.run(main())

运行结果

代码语言:javascript
复制
start main:
hello start: 1646010901.340716
------hello end : 1646010904.3481765 ----
world start: 1646010904.3481765
------world end : 1646010906.3518314 ----
-----------end start----------

从运行结果看到,并没有实现并发的效果。这是因为time.sleep()它是一个同步阻塞的模块,不是异步库,达不到并发的效果。 同样道理,之前很多同学学过的 requests 库,知道 requests 库可以发请求,于是套用上面的代码,也是达不到并发效果. 因为 requests 发送请求是串行的,即阻塞的。发送完一条请求才能发送另一条请求。

如果想实现并发请求,需用到发送 http 请求的异步库,如:aiohttp,grequests等。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 从零开始学自动化测试 微信公众号,前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 协程 coroutines
  • 协程运行三种机制
  • 并发任务的误区
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档