Python asyncio官方文档 之 高级API - 协程与任务篇

asyncio — 异步 I/O

版本:Python 3.7 +

asyncio 是用来编写 并发 代码的库,使用 async/await 关键字。

Hello World !

import asyncio

async def main():
    print('Hello ...')
    await asyncio.sleep(1)
    print('... World!')

# Python 3.7+
asyncio.run(main())

官方目录

下面是官方文档提供的asyncio 目录,看看我们都需要学习一些什么呢?

如果方便,可以参考官方文档来阅读。

---------- 参考 --------------

高层级 API

低层级 API

指南与教程

高级API - 协程与任务

好了,目录东西有点多,今天我们先来写 携程与任务

关于协程与任务,子目录

### 协程

这里是Python3.7+ , 之前的定义方式有很多, 我们统一3.7+新版本为准,不再纠结yiled from等等

后面熟悉了异步,有兴趣可以去看看。

之前使用 def 定义函数, 现在我们加上async/await 这个函数就变成了协程, 先这样理解,虽然它不对。

import asyncio

async def main():
    print('hello')
    await asyncio.sleep(1)
    print('world')

加上 async/await 变成协程以后,它不能再像普通函数一样直接调用了,

直接调用不会有预期的效果,因为函数已经变成了 coroutine 协程对象

>>> main()
<coroutine object main at 0x1053bb7c8>

想要让协程正确执行,我们就需要用到asyncio

asyncio.run(main())

现在来写一个完整的程序

import asyncio
import time

async def say_hello(sleep_time, say_what):
    await asyncio.sleep(sleep_time)
    print(say_what)
    return sleep_time

async def main():
    print(f" main start at {time.strftime('%Y-%m-%d %X')}")

    num = await say_hello(1, 'hello')
    print(num)
    num = await say_hello(2, 'world')
    print(num)

    print(f"main finished at {time.strftime('%Y-%m-%d %X')}")

asyncio.run(main())

心细的你会发现,咦,这是同步执行的呀,花费了我 3秒

没错,这就是同步的模式,

解释一下上面的代码 ,async/await 定义了一个 say_hello 协程,睡眠N秒并打印一些东西。

又定义了一个主要的协程main, 在协程中调用协程,直接使用await say_hello(1, 'hello') ,这里会阻塞住,等待调用的携程返回。

但是注意,刚刚说的阻塞,是对于main的,程序和cpu不会阻塞,是跳出这个协程

在整个事件循环中去执行其他的协程任务,直到await 接收到返回值

那么如何并发的执行两次 say_hello 呢?

使用asyncio.create_task() 并发运行多个协程。

把需要执行的携程和参数 放入 asyncio.create_task()中 ,会自动把协程变成一个任务,并且自动的启动运行

具体什么是任务,后面会提到,可以先不懂(我也不记得了!),先把它当做正在运行的协程,如果不对,后面再改。

如果我能看懂,后面我也会讲讲任务和协程的关系

(我大概记得是有这个,我是边看文档 边码字 边学习 边复习 )

import asyncio
import time

async def say_hello(sleep_time, say_what):
    await asyncio.sleep(sleep_time)
    print(say_what)
    return sleep_time

async def main():
    task1 = asyncio.create_task(
        say_hello(1, 'hello'))

    task2 = asyncio.create_task(
        say_hello(2, 'world'))

    print(f"started at {time.strftime('%Y-%m-%d %X')}")

    await task1
    await task2

    print(f"finished at {time.strftime('%Y-%m-%d %X')}")
  
asyncio.run(main())

可等待对象

可等待对象 主要有3种类型

  • 协程
  • 任务
  • Future

先来理解什么事可等待对象,async/await 还记得这个关键字吧,我们也已经使用过很多次了。

await 后面我们都跟的是什么呢? 后面跟的就是可等待对象,

那有什么效果呢?

阻塞,让出cpu,让其他协程去占用cpu,等待await后面跟着的东西返回结果,然后程序继续往下走

其实我们已经使用过两种类型了。

协程 num = await say_hello(1, 'hello')

任务 await task1, task1 是我们执行并发程序的时候,由asyncio.create_task(协程,参数) 创建的,并直就执行了,没有start()操作。

Future

剩下一个Future, 官方解释如下。

Future 是一种特殊的 低层级 可等待对象,表示一个异步操作的 最终结果

当一个 Future 对象 被等待,这意味着协程将保持等待直到该 Future 对象在其他地方操作完毕。

在 asyncio 中需要 Future 对象以便允许通过 async/await 使用基于回调的代码。

通常情况下 没有必要 在应用层级的代码中创建 Future 对象。

Future 对象有时会由库和某些 asyncio API 暴露给用户,用作可等待对象:

import asyncio
import time


async def say_hello(future, sleep_time, value):

    await asyncio.sleep(sleep_time)
    # 对future设置一个值,然后awati future 就可以收到值,结束阻塞了
    future.set_result(sleep_time)

    await asyncio.sleep(sleep_time)
    return value


async def main():
    print(f"started at {time.strftime('%Y-%m-%d %X')}")

    # 获取当前的事件循环IO/Loop loop
    loop = asyncio.get_running_loop()
    
    # 创建一个可等待对象 future
    future = loop.create_future()

    # 创建任务并执行
    task1 = loop.create_task(say_hello(future, 2, 'hello'))

    print(await future) # 阻塞,只有对future进行了set_value之后,收到值,才通过
    print(await task1)  # 阻塞,等待task1执行完毕

    print(f"finished at {time.strftime('%Y-%m-%d %X')}")

asyncio.run(main())

上面代码的详细解释都在注释里了,可以这样理解,Future 这个可等待对象

Future 可以理解成 未来,期待 ,它期待在未来的某个时间点,有人为 他进行set_value

运行 asyncio 程序

我们已经使用过了,asyncio.``run(coro, ***, debug=False)

执行 coroutine coro 并返回结果,*** 有定义协程的参数,debug是调试模式。

此函数总是会创建一个新的事件循环并在结束时关闭之。它应当被用作 asyncio 程序的主入口点,理想情况下应当只被调用一次。

事件循环就是Loop,还记得我们再贴出Future的代码时,里面有个获取当前的事件循环loop

asyncio.get_running_loop() 获取正在运行的 loop,这个时间循环最开始其实是由我们的asyncio.run()创建的。

查看源码,asyncio.run点进去,你会看到loop = events.new_event_loop() 这个应该就是创建一个新的事件循环

并且它上面还有一段代码,我们意会意会

if events._get_running_loop() is not None:
raise RuntimeError(
“asyncio.run() cannot be called from a running event loop”)

import asyncio

async def main():
    await asyncio.sleep(1)
    print('hello')

asyncio.run(main())

创建任务

asyncio.create_task(coro)

coro 协程 打包为一个 Task 排入日程准备执行。返回 Task 对象。

该任务会在 get_running_loop() 返回的循环中执行,如果当前线程没有在运行的循环则会引发 RuntimeError

此函数 在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低层级的 asyncio.ensure_future() 函数。

# Python3.7+
task = asyncio.create_task(coro())

# 之前的版本,更底层的操作,两个效果是一样的
task = asyncio.ensure_future(coro())

这里说的事 排入日程准备执行,返回任务对象。

我的理解就是 扔到事件循环中了,准备执行就是已经执行了,但是什么时候执行,还要看loop,我们控制不了。

休眠

coroutine asyncio.sleep(delay, result=None, ***, loop=None)

coroutine 就是协程的意思,告诉我们这是一个协程

阻塞 delay 指定的秒数。

如果指定了 result,则当协程完成时将其返回给调用者。

sleep() 总是会挂起当前任务,以允许其他任务运行。

loop 参数已弃用,计划在 Python 3.10 中移除。

以下协程示例运行 5 秒,每秒显示一次当前日期:

import asyncio
import datetime

async def display_date():
    loop = asyncio.get_running_loop()
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(1)

asyncio.run(display_date())

并发运行任务

awaitable asyncio.gather(*aws, loop=None, return_exceptions=False)

awatiable 官方文档中的字样,就是告诉我们asyncio.gather 返回的是一个可调用对象,前面可以接 await

然后我们看参数

aws 是个列表,这个列表中的元素都是可等待对象,*[] 就是拆包

而如果这个可等待对象是一个我们用async写的协程呢? 那么它将自动的变成一个Task任务,并且加入到事件循环loop中去执行。

所有的可等待对象都完成后,返回结果也是一个列表,且和awa位置对应。

"""
并行执行
"""
import asyncio
import time

async def say_hello(sleep_time, say_what):
    await asyncio.sleep(sleep_time)
    print(say_what)
    return sleep_time

async def main():

    print(f"started at {time.strftime('%Y-%m-%d %X')}")
    
    await asyncio.gather(
        say_hello(1, 1),
        say_hello(2, 2),
        say_hello(3, 3),
    )

    print(f"finished at {time.strftime('%Y-%m-%d %X')}")
  
asyncio.run(main())

下面一大段话 可以先别看,学会如何取消操作后,再来看也行。

然后看return_exceptions=False,官方的解释为

如果 return_exceptionsFalse (默认),所引发的首个异常会立即传播给等待 gather() 的任务。aws 序列中的其他可等待对象 不会被取消 并将继续运行。

如果 return_exceptionsTrue,异常会和成功的结果一样处理,并聚合至结果列表。

如果 gather() 被取消,所有被提交 (尚未完成) 的可等待对象也会 被取消

如果 aws 序列中的任一 Task 或 Future 对象 被取消,它将被当作引发了 CancelledError 一样处理 – 在此情况下 gather() 调用 不会 被取消。这是为了防止一个已提交的 Task/Future 被取消导致其他 Tasks/Future 也被取消。

不是很好理解,我参考别人的博客,大概猜测的是,正在运行的协程task是不能被取消的,如果取消,就会报错。

如果使用默认值,就是显示的报错了,如果改成True,虽然也不能被取消,但是报错会捕捉到结果中,最后聚合到返回的列表中。

屏蔽取消操作

这里讲的是屏蔽取消操作,那首先我们要会取消操作

"""
学会如何取消协程任务Task,以及一些小知识点
"""
import asyncio

async def do_some():
    """
    做一些事情,前后睡一秒
    """
    print("do ...")
    await asyncio.sleep(1)
    print('hello , do it !')
    await asyncio.sleep(1)

async def main():
    # 创建一个可等待对象task
    task = asyncio.ensure_future(do_some())
    await asyncio.sleep(0.5)
    task.cancel() 
    try:
        await task
    except asyncio.CancelledError:
        print(await task 报错,任务已经被取消了')

if __name__ == "__main__":
    # 创建一个 Loop 事件循环
    loop = asyncio.get_event_loop()
    # 执行,知道结束
    loop.run_until_complete(main())

屏蔽取消

awaitable asyncio.``shield(aw, ***, loop=None)

不太会用,贴上一段教程,以后再看吧。

超时

coroutine asyncio.wait_for(aw, timeout, ***, loop=None)

等待 可等待对象完成,指定等待时间。

如果这个可等待对象是个协程函数,那么他会自动执行,如果是future,那么久等待set_value

简单等待

coroutine asyncio.wait(aws, ***, loop=None, timeout=None, return_when=ALL_COMPLETED)

并行运行aws指定的 可等待对象,并阻塞,知道满足return_when的条件。

done, pending = await asyncio.wait(aws)

return_when如下

FIRST_COMPLETED函数将在任意可等待对象结束或取消时返回。
FIRST_EXCEPTION函数将在任意可等待对象因引发异常而结束时返回。当没有引发任何异常时它就相当于 ALL_COMPLETED
ALL_COMPLETED函数将在所有可等待对象结束或取消时返回。

loop 参数已弃用,计划在 Python 3.10 中移除。

如果 aws 中的某个可等待对象为协程,它将自动作为任务加入日程。但是这个方法直接向 wait() 传入协程对象已弃用,因为这会导致 令人迷惑的行为


下面复制的,还没修改翻译为自己的理解

来自其他线程的日程安排

  • asyncio.run_coroutine_threadsafe(coro, loop)

    向指定事件循环提交一个协程。线程安全。

    # 创建一个协程
    coro = asyncio.sleep(1, result=3)
    
    # 向另外的一个线程/或者另外的一个事件循环loop提交任务,返回一个future对象
    future = asyncio.run_coroutine_threadsafe(coro, loop)
    
    # future.result() 获取另外一个线程返回的结果,timeout可以指定超时时间
    # 如果结果不等于3就报错
    assert future.result(timeout) == 3
    

    如果在协程内产生了异常,将会通知返回的 Future 对象。它也可被用来取消事件循环中的任务:

    try:
        result = future.result(timeout)
    except asyncio.TimeoutError: # 超时的异常
        print('The coroutine took too long, cancelling the task...')
        future.cancel() # 取消任务
    except Exception as exc: # 报错了
        print(f'The coroutine raised an exception: {exc!r}')
    else:
        print(f'The coroutine returned: {result!r}')
    

内省

  • asyncio.current_task(loop=None)

    返回当前运行的 Task 实例,如果没有正在运行的任务则返回 None

    如果 loopNone 则会使用 get_running_loop() 获取当前事件循环。3.7 新版功能.

  • asyncio.all_tasks(loop=None)

    返回事件循环所运行的未完成的 Task 对象的集合。

    如果 loopNone,则会使用 get_running_loop() 获取当前事件循环。3.7 新版功能.

Task 对象

  • class asyncio.Task(coro, ***, loop=None)

    一个类似Future的对象,非线程安全。

    Task 对象被用来在事件循环中运行协程。如果一个协程在等待一个 Future 对象,Task 对象会挂起该协程的执行并等待该 Future 对象完成。当该 Future 对象 完成,被打包的协程将恢复执行。

    事件循环使用协同日程调度: 一个事件循环每次运行一个 Task 对象。而一个 Task 对象会等待一个 Future 对象完成,该事件循环会运行其他 Task、回调或执行 IO 操作。

    使用高层级的 asyncio.create_task() 函数来创建 Task 对象,也可用低层级的 loop.create_task()ensure_future() 函数。不建议手动实例化 Task 对象。

    要取消一个正在运行的 Task 对象可使用 cancel() 方法。调用此方法将使该 Task 对象抛出一个 CancelledError 异常给打包的协程。如果取消期间一个协程正在等待一个 Future 对象,该 Future 对象也将被取消。

    cancelled() 可被用来检测 Task 对象是否被取消。如果打包的协程没有抑制 CancelledError 异常并且确实被取消,该方法将返回 True

    asyncio.TaskFuture 继承了其除 Future.set_result()Future.set_exception() 以外的所有 API。

    Task 对象支持 contextvars 模块。当一个 Task 对象被创建,它将复制当前上下文,然后在复制的上下文中运行其协程。

  • cancel() 请求取消 Task 对象

  • cancelled() 如果 Task 对象 被取消 则返回 True。

    当使用 cancel() 发出取消请求时 Task 会被 取消,其封包的协程将传播被抛入的 CancelledError 异常。

  • done() 如果 Task 对象 已完成 则返回 True。

    当 Task 所封包的协程返回一个值、引发一个异常或 Task 本身被取消时,则会被认为 已完成。

  • result() 返回 Task 的结果。
    如果 Task 对象 已完成,其封包的协程的结果会被返回 (或者当协程引发异常时,该异常会被重新引发。)
    如果 Task 对象 被取消,此方法会引发一个 CancelledError 异常。
    如果 Task 对象的结果还不可用,此方法会引发一个 InvalidStateError 异常。

  • exception() 返回 Task 对象的异常。

    如果所封包的协程引发了一个异常,该异常将被返回。如果所封包的协程正常返回则该方法将返回 None。

    如果 Task 对象 被取消,此方法会引发一个 CancelledError 异常。

    如果 Task 对象尚未 完成,此方法将引发一个 InvalidStateError 异常。

  • add_done_callback(callback, *, context=None)
    添加一个回调,将在 Task 对象 完成 时被运行。
    此方法应该仅在低层级的基于回调的代码中使用。
    要了解更多细节请查看 Future.add_done_callback() 的文档。

  • remove_done_callback(callback)
    从回调列表中移除 callback 。
    此方法应该仅在低层级的基于回调的代码中使用。
    要了解更多细节请查看 Future.remove_done_callback() 的文档。

  • get_stack(*, limit=None)
    返回此 Task 对象的栈框架列表。
    如果所封包的协程未完成,这将返回其挂起所在的栈。如果协程已成功完成或被取消,这将返回一个空列表。如果协程被一个异常终止,这将返回回溯框架列表。
    框架总是从按从旧到新排序。
    每个被挂起的协程只返回一个栈框架。
    可选的 limit 参数指定返回框架的数量上限;默认返回所有框架。返回列表的顺序要看是返回一个栈还是一个回溯:栈返回最新的框架,回溯返回最旧的框架。(这与 traceback 模块的行为保持一致。)

  • print_stack(*, limit=None, file=None)
    打印此 Task 对象的栈或回溯。
    此方法产生的输出类似于 traceback 模块通过 get_stack() 所获取的框架。
    limit 参数会直接传递给 get_stack()。
    file 参数是输出所写入的 I/O 流;默认情况下输出会写入 sys.stderr。

  • classmethod all_tasks(loop=None)
    返回一个事件循环中所有任务的集合。
    默认情况下将返回当前事件循环中所有任务。如果 loop 为 None,则会使用 get_event_loop() 函数来获取当前事件循环。
    此方法 已弃用 并将在 Python 3.9 中移除。请改用 asyncio.all_tasks() 函数。

  • classmethod current_task(loop=None)
    返回当前运行任务或 None。
    如果 loop 为 None,则会使用 get_event_loop() 函数来获取当前事件循环。
    此方法 已弃用 并将在 Python 3.9 中移除。请改用 asyncio.current_task() 函数。

基于生成器的协程

对基于生成器的协程的支持 已弃用 并计划在 Python 3.10 中移除。

已标记关键词 清除标记
简介: 历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流! 核心技术栈列表: 值得介绍的是,本课在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课内容与收益: 总的来说,本课是一门具有很强实践性质的“项目实战”课,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流,如下图所示:
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页