一文速通Python并行计算:14 Python异步编程-协程的管理和调度

一文速通 Python 并行计算:14 Python 异步编程-协程的管理和调度

image

摘要:

Python 异步编程基于 async/await 构建协程,运行在事件循环中。协程生成 Task,遇到 await 时挂起,I/O 完成触发回调恢复运行,通过事件循环非阻塞调度并发任务,实现单线程高并发。

image

关于我们更多介绍可以查看云文档:Freak 嵌入式工作室云文档,或者访问我们的 wiki:****https://github.com/leezisheng/Doc/wik

原文链接:

FreakStudio的博客

往期推荐:

学嵌入式的你,还不会面向对象??!

全网最适合入门的面向对象编程教程:00 面向对象设计方法导论

全网最适合入门的面向对象编程教程:01 面向对象编程的基本概念

全网最适合入门的面向对象编程教程:02 类和对象的 Python 实现-使用 Python 创建类

全网最适合入门的面向对象编程教程:03 类和对象的 Python 实现-为自定义类添加属性

全网最适合入门的面向对象编程教程:04 类和对象的Python实现-为自定义类添加方法

全网最适合入门的面向对象编程教程:05 类和对象的Python实现-PyCharm代码标签

全网最适合入门的面向对象编程教程:06 类和对象的Python实现-自定义类的数据封装

全网最适合入门的面向对象编程教程:07 类和对象的Python实现-类型注解

全网最适合入门的面向对象编程教程:08 类和对象的Python实现-@property装饰器

全网最适合入门的面向对象编程教程:09 类和对象的Python实现-类之间的关系

全网最适合入门的面向对象编程教程:10 类和对象的Python实现-类的继承和里氏替换原则

全网最适合入门的面向对象编程教程:11 类和对象的Python实现-子类调用父类方法

全网最适合入门的面向对象编程教程:12 类和对象的Python实现-Python使用logging模块输出程序运行日志

全网最适合入门的面向对象编程教程:13 类和对象的Python实现-可视化阅读代码神器Sourcetrail的安装使用

全网最适合入门的面向对象编程教程:全网最适合入门的面向对象编程教程:14 类和对象的Python实现-类的静态方法和类方法

全网最适合入门的面向对象编程教程:15 类和对象的 Python 实现-__slots__魔法方法

全网最适合入门的面向对象编程教程:16 类和对象的Python实现-多态、方法重写与开闭原则

全网最适合入门的面向对象编程教程:17 类和对象的Python实现-鸭子类型与“file-like object“

全网最适合入门的面向对象编程教程:18 类和对象的Python实现-多重继承与PyQtGraph串口数据绘制曲线图

全网最适合入门的面向对象编程教程:19 类和对象的 Python 实现-使用 PyCharm 自动生成文件注释和函数注释

全网最适合入门的面向对象编程教程:20 类和对象的Python实现-组合关系的实现与CSV文件保存

全网最适合入门的面向对象编程教程:21 类和对象的Python实现-多文件的组织:模块module和包package

全网最适合入门的面向对象编程教程:22 类和对象的Python实现-异常和语法错误

全网最适合入门的面向对象编程教程:23 类和对象的Python实现-抛出异常

全网最适合入门的面向对象编程教程:24 类和对象的Python实现-异常的捕获与处理

全网最适合入门的面向对象编程教程:25 类和对象的Python实现-Python判断输入数据类型

全网最适合入门的面向对象编程教程:26 类和对象的Python实现-上下文管理器和with语句

全网最适合入门的面向对象编程教程:27 类和对象的Python实现-Python中异常层级与自定义异常类的实现

全网最适合入门的面向对象编程教程:28 类和对象的Python实现-Python编程原则、哲学和规范大汇总

全网最适合入门的面向对象编程教程:29 类和对象的Python实现-断言与防御性编程和help函数的使用

全网最适合入门的面向对象编程教程:30 Python的内置数据类型-object根类

全网最适合入门的面向对象编程教程:31 Python的内置数据类型-对象Object和类型Type

全网最适合入门的面向对象编程教程:32 Python的内置数据类型-类Class和实例Instance

全网最适合入门的面向对象编程教程:33 Python的内置数据类型-对象Object和类型Type的关系

全网最适合入门的面向对象编程教程:34 Python的内置数据类型-Python常用复合数据类型:元组和命名元组

全网最适合入门的面向对象编程教程:35 Python的内置数据类型-文档字符串和__doc__属性

全网最适合入门的面向对象编程教程:36 Python的内置数据类型-字典

全网最适合入门的面向对象编程教程:37 Python常用复合数据类型-列表和列表推导式

全网最适合入门的面向对象编程教程:38 Python常用复合数据类型-使用列表实现堆栈、队列和双端队列

全网最适合入门的面向对象编程教程:39 Python常用复合数据类型-集合

全网最适合入门的面向对象编程教程:40 Python常用复合数据类型-枚举和enum模块的使用

全网最适合入门的面向对象编程教程:41 Python常用复合数据类型-队列(FIFO、LIFO、优先级队列、双端队列和环形队列)

全网最适合入门的面向对象编程教程:42 Python常用复合数据类型-collections容器数据类型

全网最适合入门的面向对象编程教程:43 Python常用复合数据类型-扩展内置数据类型

全网最适合入门的面向对象编程教程:44 Python内置函数与魔法方法-重写内置类型的魔法方法

全网最适合入门的面向对象编程教程:45 Python实现常见数据结构-链表、树、哈希表、图和堆

全网最适合入门的面向对象编程教程:46 Python函数方法与接口-函数与事件驱动框架

全网最适合入门的面向对象编程教程:47 Python函数方法与接口-回调函数Callback

全网最适合入门的面向对象编程教程:48 Python函数方法与接口-位置参数、默认参数、可变参数和关键字参数

全网最适合入门的面向对象编程教程:49 Python函数方法与接口-函数与方法的区别和lamda匿名函数

全网最适合入门的面向对象编程教程:50 Python函数方法与接口-接口和抽象基类

全网最适合入门的面向对象编程教程:51 Python函数方法与接口-使用Zope实现接口

全网最适合入门的面向对象编程教程:52 Python函数方法与接口-Protocol协议与接口

全网最适合入门的面向对象编程教程:53 Python字符串与序列化-字符串与字符编码

全网最适合入门的面向对象编程教程:54 Python字符串与序列化-字符串格式化与format方法

全网最适合入门的面向对象编程教程:55 Python字符串与序列化-字节序列类型和可变字节字符串

全网最适合入门的面向对象编程教程:56 Python字符串与序列化-正则表达式和re模块应用

全网最适合入门的面向对象编程教程:57 Python字符串与序列化-序列化与反序列化

全网最适合入门的面向对象编程教程:58 Python字符串与序列化-序列化Web对象的定义与实现

全网最适合入门的面向对象编程教程:59 Python并行与并发-并行与并发和线程与进程

一文速通Python并行计算:00 并行计算的基本概念

一文速通Python并行计算:01 Python多线程编程-基本概念、切换流程、GIL锁机制和生产者与消费者模型

一文速通Python并行计算:02 Python多线程编程-threading模块、线程的创建和查询与守护线程

一文速通Python并行计算:03 Python多线程编程-多线程同步(上)—基于互斥锁、递归锁和信号量

一文速通Python并行计算:04 Python多线程编程-多线程同步(下)—基于条件变量、事件和屏障

一文速通Python并行计算:05 Python多线程编程-线程的定时运行

一文速通Python并行计算:06 Python多线程编程-基于队列进行通信

一文速通Python并行计算:07 Python多线程编程-线程池的使用和多线程的性能评估

一文速通Python并行计算:08 Python多进程编程-进程的创建命名、获取ID、守护进程的创建和终止进程

一文速通Python并行计算:09 Python多进程编程-进程之间的数据同步-基于互斥锁、递归锁、信号量、条件变量、事件和屏障

一文速通Python并行计算:10 Python多进程编程-进程之间的数据共享-基于共享内存和数据管理器

一文速通Python并行计算:11 Python多进程编程-进程之间的数据安全传输-基于队列和管道

一文速通Python并行计算:12 Python多进程编程-进程池Pool

一文速通Python并行计算:13 Python异步编程-基本概念与事件循环和回调机制

更多精彩内容可看:

C语言一点五编程实战:纯 C 的模块化×继承×多态框架

给你的 Python 加加速:一文速通 Python 并行计算

一文搞懂 CM3 单片机调试原理

肝了半个月,嵌入式技术栈大汇总出炉

电子计算机类比赛的“武林秘籍”

一个MicroPython的开源项目集锦:awesome-micropython,包含各个方面的Micropython工具库

Avnet ZUBoard 1CG开发板—深度学习新选择

工程师不要迷信开源代码,还要注重基本功

什么?配色个性化的电机驱动模块?!!

什么?XIAO主控新出三款扩展板!

手把手教你实现Arduino发布第三方库

万字长文手把手教你实现MicroPython/Python发布第三方库

一文速通电子设计大赛,电子人必看的获奖秘籍

一文速通光电设计大赛,电子人必看!

工科比赛“无脑”操作指南:知识学习硬件选购→代码调试→报告撰写的保姆级路线图

单场会议拍摄收费6000+?拍摄技巧和步骤都在这里

0基础如何冲击大唐杯国奖?学姐的的备赛心得都在这里

爆肝整理长文】大学生竞赛速通指南:选题 × 组队 × 路演 48 小时备赛搞定

当代大学生竞赛乱象:从“内卷”到“祖传项目”的生存指南

女大学生摆摊亏损5000元踩点实录:成都哪里人最多、最容易赚到钱?我告诉你!

用拍立得在成都网红打卡点赚钱:一份超实用地摊级旅游副业教程

成都印象:一个电子女孩亲手做了点浪漫

普通继电器 vs 磁保持继电器 vs MOS管:工作原理与电路设计全解析

告别TP4056!国产SY3501D单芯片搞定充放电+升压,仅需7个元件!附开源PCB文件

POB面向老板编程—现实驱动的新型编程范式

文档获取:

可访问如下链接进行对文档下载:

https://github.com/leezisheng/Doc

该文档是一份关于 并行计算Python 并发编程 的学习指南,内容涵盖了并行计算的基本概念、Python 多线程编程、多进程编程以及协程编程的核心知识点:

image

正文

在上文提到的例子中,我们看到当一个程序变得很大而且复杂时,将其划分为子程序,每一部分实现特定的任务是个不错的方案。子程序不能单独执行,只能在主程序的请求下执行,主程序负责协调使用各个子程序。协程就是子程序的泛化。和子程序一样的事,协程只负责计算任务的一步;和子程序不一样的是,协程没有主程序来进行调度。这是因为协程通过管道连接在一起,没有监视函数负责顺序调用它们。

对于子程序来说,调用是通过栈实现的,子程序调用总是一个入口,一次返回,调用顺序是明确的。比如 A 调用 B,B 在执行过程中又调用了 C,C 执行完毕返回,B 执行完毕返回,最后是 A 执行完毕。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。从心理学的角度看,协程类似于人类的“任务切换”能力,我们可以暂时将注意力从一个任务转移到另一个,然后再回来继续未完成的任务。

或者说,**在协程中,执行点可以被挂起,可以被从之前挂起的点恢复执行。**通过协程池就可以插入到计算中:运行第一个任务,直到它返回(yield)执行权,然后运行下一个,这样顺着执行下去。这种插入的控制组件就是前文介绍的事件循环。它持续追踪所有的协程并执行它们。

协程的另外一些重要特性如下:

  • 协程可以有多个入口点,并可以 yield 多次;
  • 协程可以将执行权交给其他协程

yield 表示协程在此暂停,并且将执行权交给其他协程。因为协程可以将值与控制权一起传递给另一个协程,所以“yield 一个值”就表示将值传给下一个执行的协程。

协程与传统的线程或进程相比,有几个关键区别:

**(1)轻量级:**协程通常是用户态的,子程序切换(函数)不是线程切换,由程序自身控制,切换开销比系统线程小得多。

**(2)非抢占式:**协程的切换是协作式的,即协程需要显式地 yield 来让出控制权。

**(3)更好的控制:**协程提供了更细粒度的控制,如何以及何时切换是由程序员或协程库决定的。

协程可以处理 IO 密集型程序的效率问题,但是处理 CPU 密集型不是它的长处,如要充分发挥 CPU 利用率可以结合多进程 + 协程。

1.使用 Asyncio 管理协程

Python3.x 提供了如下方式实现协程:

  • asyncio + yield from (python3.4+) :

asyncio 是 Python3.4 版本引入的标准库,直接内置了对异步 IO 的支持。asyncio 的异步操作,需要在 coroutine 中通过 yield from 完成。

import asyncio@asyncio.coroutine
def test(i):print('test_1', i)r = yield from asyncio.sleep(1)print('test_2', i)if __name__ == '__main__':loop = asyncio.get_event_loop()tasks = [test(i) for i in range(3)]loop.run_until_complete(asyncio.wait(tasks))loop.close()

asyncio.coroutine 使用装饰器,定义了一个协程。所谓装饰器是给现有的模块增添新的小功能的函数,它可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。

使用 @asyncio.coroutine 定义协程的通用方法如下:

import asyncio@asyncio.coroutine
def coroutine_function(function_arguments):_# DO_SOMETHING_

以上代码将 test(i) 定义为一个协程,然后就把这个协程放到事件循环中执行。test(i) 首先执行打印操作,这里用 asyncio.sleep(1) 模拟一个耗时 1 秒的 IO 操作,asyncio.sleep() 本身也是一个协程,这里使用 yield from 语法调用 asyncio.sleep(),但注意线程不会等待 asyncio.sleep(),而是直接中断并执行下一个消息循环。当 asyncio.sleep() 返回时,线程就可以从 yield from 拿到返回值(此处是 None),然后接着执行下一行语句。由此实现异步执行。

  • asyncio + async/await (python3.5+):

为了简化并更好地标识异步 IO,从 Python3.5 开始引入了新的语法 async 和 await,可以让 coroutine 的代码更简洁易读。请注意,async 和 await 是 coroutine 的新语法,使用新语法只需要做两步简单的替换:

  • 把 @asyncio.coroutine 替换为 async;
  • 把 yield from 替换为 await,即让出当前的执行权,等待的对象有结果返回时,再重新获得可以被继续执行的权利,只有可等待对象才能被 await。

注意,包含 @asyncio.coroutine 装饰器的将从 Python3.11 中删除,因此 asyncio 模块没有 @asyncio.coroutine 装饰符。

import asyncio
async def test(i):print('test_1', i)await asyncio.sleep(1)print('test_2', i)if __name__ == '__main__':loop = asyncio.new_event_loop()asyncio.set_event_loop(loop)_# 在python3.8后直接把协程对象传给asyncio.wait()是不行的,__# 必须封装成tasks对象传入_tasks = [loop.create_task(test(1)),loop.create_task(test(2)),loop.create_task(test(3))]loop.run_until_complete(asyncio.wait(tasks))loop.close()

image

除了 await 方法,asyncio 提供了**asyncio.run()**来执行协程:

run() 函数接收一个协程对象,在执行时,总会**创建一个新的事件循环,并在结束后关闭循环。****理想情况下,****run() ****函数应当被作为程序的总入口,并且只会被调用一次。**如果同一线程中还有其它事件循环在运行,则此方法不能被调用。

image

2.使用 Asyncio 控制任务

Asyncio 是用来处理事件循环中的异步进程和并发任务执行的。**它还提供了 asyncio.Task() 类,可以在任务中使用协程。**它的作用是,在同一事件循环中,**运行某一个任务的同时可以并发地运行多个任务。当协程被包在任务中,它会自动将任务和事件循环连接起来,当事件循环启动的时候,任务自动运行。**这样就提供了一个可以自动驱动协程的机制。

image

2.1 asyncio.create_task()

image

下面的例子中我们使用 asyncio.create_task() 创建 Task,create_task() 会把一个协程打包成一个任务(Task),并立即排入日程准备执行,函数返回值是打包完成的 Task 对象。

import asyncio
import time
async def foo(n):await asyncio.sleep(n)async def main():task1 = asyncio.create_task(foo(1))task2 = asyncio.create_task(foo(2))t1 = time.time()print('hello')await task1await task2print('coroutine')t2 = time.time()print('cost:', t2 - t1)asyncio.run(main())

如下为运行结果:

image

当使用 create_task() 时,创建的任务立即被加入到事件循环中,并不会阻塞当前的程序,所以上述程序在打印出 hello 后只需等待 2 秒就打印出 coroutine

2.2 asyncio.gather()

asyncio.gather() 函数允许调用者将多个可等待对象组合在一起。分组后,可等待对象可以并发执行、等待和取消。它是一个有用的实用函数,可用于分组和执行多个协程或多个任务。

image

从功能上看,asyncio.waitasyncio.gather 实现的效果是相同的,都是把所有 Task 任务结果收集起来。但不同的是,asyncio.wait 会返回两个值:donependingdone 为已完成的协程 Taskpending 为超时未完成的协程 Task,需通过 future.result 调用 Taskresult;而 asyncio.gather返回的是所有已完成Taskresult**,不需要再进行调用或其他操作,就可以得到全部结果。**

import asyncioasync def foo():return 'foo'
async def bar():raise RuntimeError('fake runtime error')
async def main():task1 = asyncio.create_task(foo())task2 = asyncio.create_task(bar())_# return_exceptions=True__# 如果return_exceptions为True,__# 异常会和成功的结果一样处理,并聚合至结果列表_results = await asyncio.gather(task1, task2, return_exceptions=True)print(results)_# 返回结果的顺序和传参顺序一致__# isinstance() 函数来判断一个对象是否是一个已知的类型__# isinstance(object, classinfo)_assert isinstance(results[1], RuntimeError)try:_# 如果return_exceptions为False(默认)__# 所引发的首个异常会立即传播给等待gather()的任务_results = await asyncio.gather(task1, task2, return_exceptions=False)_# 此处打印并不会被执行, results 也未被赋值_print(results)except RuntimeError as runtime_err:_# 捕获异常并打印: fake runtime error_print(runtime_err)asyncio.run(main())

执行结果如下:

image

2.3 asyncio.wait()

asyncio.wait() 函数可用于等待一组异步任务完成,回想一下,asyncio 任务是包装协程的 asyncio.Task 类的一个实例。它允许独立调度和执行协程,Task 实例提供任务句柄以查询状态和获取结果。**wait()**函数允许我们等待一组任务完成。等待调用可以配置为等待不同的条件,例如所有任务完成、第一个任务完成以及第一个任务因错误而失败。

image

asyncio.wait 最常见的写法是:await asyncio.wait(task_list),表示运行直到所有给定的协程都完成。常见写法为:

...
_# create many tasks_
tasks = [asyncio.create_task(task_coro(i)) for i in range(10)]

示例代码如下:

import asyncioasync def foo():await asyncio.sleep(3)return 'foo'
async def bar():await asyncio.sleep(1)return 'bar'
async def main():task1 = asyncio.create_task(foo())task2 = asyncio.create_task(bar())_# 有一个任务执行完成即返回, 总共耗时 1 秒_done, pending = await asyncio.gather(task1, task2, return_exceptions=True)_# done 集合里包含打包成 Task 的 bar()_print(f'done: {done}')_# pendding 集合里包含打包成 Task 的 foo()_print(f'pending: {pending}')_# 所有任务执行完成后返回, 总共耗时 3 秒_done, pending = await asyncio.gather(task1, task2, return_exceptions=True)_# done 集合里包含被带打包成 Task 的 foo() 和 bar()_print(f'done: {done}')_# pending 集合为空_print(f'pending: {pending}')_# 所有任务执行完成, 但运行时间不能超 2 秒后返回, 总共耗时 2 秒_done, pending = await asyncio.gather(task1, task2, return_exceptions=True)_# done 集合里包含打包成 Task 的 bar()_print(f'done: {done}')_# pendding 集合里包含打包成 Task 的 foo()_print(f'pending: {pending}')
asyncio.run(main())

image

2.4 asyncio.as_completed()

有时,我们必须在完成一个后台任务后立即开始下面的动作。比如我们爬取一些数据,马上调用机器学习模型进行计算,gather 方法不能满足我们的需求,但是我们可以使用 as_completed 方法。

image

as_completed 不是并发方法,接受 aws 集合,返回一个带有 yield 语句的迭代器。所以我们可以直接遍历每个完成的后台任务,我们可以对每个任务单独处理异常,而不影响其他任务的执行:

示例代码如下:

import asyncioasync def foo():await asyncio.sleep(2)return 'foo'async def bar():await asyncio.sleep(1)return 'bar'async def main():for fut in asyncio.as_completed({foo(), bar()}):earliest_result = await fut_# 会依次打印 bar 和 foo, 因为 bar() 会更早执行完毕_print(earliest_result)asyncio.run(main())

image

以上介绍多任务并发时引入了超时的概念,超时也可以被应用在单独的一个任务中,使用 asyncio.wait_for(aw, timeout) 函数,该函数接受一个任务 aw 和超时时间 timeout,如果在限制时间内完成,则会正常返回,否则会被取消并抛出 asyncio.TimeoutError 异常。

为了防止任务被取消,可以使用 asyncio.shield(aw) 进行保护。shield() 会屏蔽外部取消操作,如果外部任务被取消,其内部正在执行的任务不会被取消,在内部看来取消操作并没有发生,由于内部仍正常执行,执行完毕后会触发异常,如果确保程序能忽略异常继续执行,需要在外部使用 try-except 捕获异常。如果在任务内部取消,则会被成功取消。

Asyncio 模块为我们提供了 asyncio.Task(coroutine) 方法来处理计算任务,它可以调度协程的执行。任务对协程对象在事件循环的执行负责。如果被包裹的协程要从 future(就是协程的实例化对象)调度,那么任务会被挂起,等待 future 的计算结果。

future 计算完成,被包裹的协程将会拿到 future 返回的结果或异常继续执行。另外,需要注意的是,事件循环一次只能运行一个任务,除非还有其它事件循环在不同的线程并行运行,此任务才有可能和其他任务并行。当一个任务在等待 future 执行的期间,事件循环会运行一个新的任务。

在下面的代码中,我们展示了三个可以被 Asyncio.Task() 并发执行的数学函数,在这个例子中,我们定义了三个协程, factorial, fibonaccibinomialCoeff ,为了能并行执行这三个任务,我们将其放到一个 tasklist 中得到事件循环然后运行任务,最后,关闭事件循环。

import asyncioasync def factorial(number):f = 1for i in range(2, number + 1):print("Asyncio.Task: Compute factorial(%s)" % (i))await asyncio.sleep(1)f *= iprint("Asyncio.Task - factorial(%s) = %s" % (number, f))async def fibonacci(number):a, b = 0, 1for i in range(number):print("Asyncio.Task: Compute fibonacci (%s)" % (i))await asyncio.sleep(1)a, b = b, a + bprint("Asyncio.Task - fibonacci(%s) = %s" % (number, a))async def binomialCoeff(n, k):result = 1for i in range(1, k+1):result = result * (n-i+1) / iprint("Asyncio.Task: Compute binomialCoeff (%s)" % (i))await asyncio.sleep(1)print("Asyncio.Task - binomialCoeff(%s , %s) = %s" % (n, k, result))if __name__ == "__main__":tasks = [asyncio.Task(factorial(10)),asyncio.Task(fibonacci(10)),asyncio.Task(binomialCoeff(20, 10))]loop = asyncio.get_event_loop()loop.run_until_complete(asyncio.wait(tasks))loop.close()

运行结果如下:

image

3.使用 Asyncio 和 Futures

future 是一个 Python 对象,它包含一个你希望在未来某个时间点获得、但目前还不存在的值。通常,当创建 future 时,它没有任何值,因为它还不存在。在这种状态下,它被认为是不完整的、未解决的或根本没有完成的。然后一旦你得到一个结果,就可以设置 future 的值,这将完成 future。那时,我们可以认为它已经完成,并可从 future 中提取结果。

image

要操作 Asyncio 中的 Future ,必须进行以下声明:

import asyncio
future = asyncio.Future()

基本的方法有:

方法作用
cancel()取消 future 的执行,调度回调函数
result()返回 future 代表的结果
exception()返回 future 中的 Exception
add_done_callback(fn)添加一个回调函数,当 future 执行的时候会调用这个回调函数
remove_done_callback(fn)从“call whten done”列表中移除所有 callback 的实例
set_result(result)将 future 标为执行完成,并且设置 result 的值
set_exception(exception)将 future 标为执行完成,并设置 Exception
import asyncio_# asyncio 里面有一个类 Future,实例化之后即可得到 future 对象_
_# 然后 asyncio 里面还有一个类 Task,实例化之后即可得到 task 对象(也就是任务)_
_# 这个 Task 是 Future 的子类,所以我们用的基本都是 task 对象,而不是 future 对象_
_# 但 Future 这个类和 asyncio 的实现有着密不可分的关系,所以我们必须单独拿出来说_future = asyncio.Future()
print(future)  _# <Future pending>_
print(future.__class__)  _# <class '_asyncio.Future'>_
print(f"future 是否完成: {future.done()}")  _# future 是否完成: False__# 设置一个值,通过 set_result_
future.set_result("古明地觉")
print(f"future 是否完成: {future.done()}")  _# future 是否完成: True_
print(future)  _# <Future finished result='古明地觉'>_
print(f"future 的返回值: {future.result()}")  _# future 的返回值: 古明地觉_

可通过调用其类型对象 Future 来创建 future,此时 future 上将没有结果集,因此调用其 done 方法将返回 False。此后用 set_result 方法设置 future 的值,这将把 future 标记为已完成。或者,如果想在 future 中设置一个异常,可调用 set_exception(必须在调用set_result(设置结果)之后才能调用result(获取结果),并且set_result只能调用一次,但result可以调用多次)

在下面的示例代码中,我们定义了一个函数 make_request,该函数里面创建了一个 future 和一个任务,该任务将在 1 秒后异步设置 future 的结果。然后在主函数中调用 make_request,当调用它时,将立即得到一个没有结果的 future。然后 await future 会让主协程陷入等待,并将执行权交出去。一旦当 future 有值了,那么再恢复 main() 协程,拿到返回值进行处理。

import asyncioasync def set_future_value(future):await asyncio.sleep(1)future.set_result("Hello World")
def make_request():future = asyncio.Future()_# 创建一个任务来异步设置 future 的值_asyncio.create_task(set_future_value(future))return future
async def main():_# 注意这里的 make_request,它是一个普通的函数,如果在外部直接调用肯定是会报错的__# 因为没有事件循环,在执行 set_future_value 时会报错__# 但如果在协程里面调用是没问题的,因为协程运行时,事件循环已经启动了__# 此时在 make_request 里面,会启动一个任务_future = make_request()print(f"future 是否完成: {future.done()}")_# 阻塞等待,直到 future 有值,什么时候有值呢?__# 显然是当协程 set_future_value 里面执行完 future.set_result 的时候_value = await future  _# 暂停 main(),直到 future 的值被设置完成_print(f"future 是否完成: {future.done()}")print(value)
asyncio.run(main())

image

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/918989.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/918989.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Node.js面试题及详细答案120题(16-30) -- 核心模块篇

《前后端面试题》专栏集合了前后端各个知识模块的面试题&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

RabbitMQ:Windows版本安装部署

目录一、概述二、OPT三、安装RabbitMQ四、登录测试一、概述 什么是MQ&#xff0c;有什么做作用&#xff1f; MQ即MessageQueue&#xff0c;消息队列。可以分为两部分理解&#xff1a;消息Message用于在不同的应用程序中传递数据。队列Queue&#xff0c;一种FIFO先进先出的数据…

酒店行业安全体系构建与优化策略

酒店行业安全体系构建与优化策略为确保酒店行业领导及宾客的安全&#xff0c;构建全面的治安联防体系及事故处理预案至关重要。某招待所通过设立保卫部&#xff0c;细化内保、治安、防火及交通管理职能&#xff0c;并下设警卫班、监控中心和电瓶车班&#xff0c;以全方位保障安…

python30-正则表达式

在Python中需要通过正则表达式对字符串进⾏匹配的时候&#xff0c;可以使⽤⼀个python自带的模块&#xff0c;名字为re。 re模块的使用&#xff1a;import re 一、匹配函数 1-1、re.match函数&#xff1a;返回匹配对象 match函数实现的是精准匹配&#xff0c;尝试从字符串的…

EP1C12F324I7N Altera Cyclone FPGA

EP1C12F324I7N 是 阿尔特拉 Altera Cyclone 系列中的一款 SRAM-based FPGA&#xff0c;定位为低成本、低功耗、面向嵌入式与消费/工业类量产应用的器件。该器件提供约 12,060 个逻辑单元&#xff08;Logic Elements&#xff09;&#xff0c;片上嵌入式存储约 234 kbit&#xff…

html5语义元素

1、参考&#xff1a;HTML5 语义元素 | 菜鸟教程 2、实战 HTML5 <section> 元素 <section> 标签定义文档中的节&#xff08;section、区段&#xff09;。比如章节、页眉、页脚或文档中的其他部分。 根据W3C HTML5文档: section 包含了一组内容及其标题。 <!D…

java调用PyTorch 训练模型实现神经网络全流程

以下是完整的操作流程:用 PyTorch 训练模型 → 导出为 ONNX 格式 → 用 Java 加载并推理,兼顾开发效率(PyTorch 快速训练)和生产部署(Java 稳定运行)。 一、PyTorch 训练模型并导出为 ONNX 1. 安装依赖 bash pip install torch onnx # PyTorch 和 ONNX 库2. 训练一个…

Maven - Spring Boot 项目打包本地 jar 的 3 种方法

文章目录Pre概述方案思路构建流程图工作机制说明目录结构示例POM 配置模板构建与验证注意事项方案优缺点Pre Maven - Manual Maven JAR Installation&#xff1a;用 mvn install:install-file 安装本地 JAR 的实用指南 概述 在 Spring Boot 项目中&#xff0c;通常依赖包会从…

平替 Claude Code,API接入 GPT-5,Codex CLI 国内直接使用教程

最新升级接入GPT-5的 Codex 拥有可以媲美 Claude Code 的AI编码能力&#xff0c;本文将指导你在 Windows系统上部署原生的 Codex CLI程序&#xff0c;并且接入超低价中转API&#xff0c;让你在国内直接用上超高性价比的 OpenAI Codex CLI 应用。关于 CodexCodex 是 OpenAI 开发…

kubernertes (K8S)部署

参考&#xff1a; https://blog.csdn.net/yu33575/article/details/135387548 二进制安装k8s&#xff1a; https://blog.csdn.net/qq_73990369/article/details/143217084 K8S二进制安装与部署 &#xff1a;https://blog.csdn.net/fantuan_sss/article/details/139073366 k8s…

LeetCode 简单JS刷题

目录 返回数组最后一个元素 2787.将一个数字表示成幂的和的方案数 326.3的幂 1780.判断一个数字是否可以表示成三的幂的和 342.4的幂 返回数组最后一个元素 1.请你编写一段代码实现一个数组方法&#xff0c;使任何数组都可以调用 array.last() 方法&#xff0c;这个方法将…

七大排序算法全解析:从入门到精通

目录 一.排序的概念 二.常见排序算法的实现 2.1 插入排序 &#xff08;1&#xff09;直接插入排序&#xff1a; 当插入第i(i>1)个元素时&#xff0c;前面的array[0],array[1],…,array[i-1]已经排好序&#xff0c;此时用array[i]的排序码与array[i-1],array[i-2],…的排序…

20250814在荣品RD-RK3588开发板的Android13下解决卡迪的LCD屏在开机的时候brightness最暗【背光的pwm信号的极性反了】

20250814在荣品RD-RK3588开发板的Android13下解决卡迪的LCD屏在开机的时候brightness最暗【背光的pwm信号的极性反了】 2025/8/14 11:33缘起&#xff1a;在荣品RD-RK3588开发板的Android13下&#xff0c;卡迪的LCD屏在开机的时候很暗&#xff0c;几乎看不见。 在命令行查看亮度…

Flink的状态管理

一、状态的概念Flink的状态其实你就可以将其想象为中间结果就可以了。在Flink中&#xff0c;算子的任务可以分为无状态和有状态两种情况。无状态算子任务在计算过程中是不依赖于其他数据的&#xff0c;只根据当前的输入数据就可以得到结果输出。比如之前讲到的Map、FlatMap、Fi…

GoLand 项目从 0 到 1:第八天 ——GORM 命名策略陷阱与 Go 项目启动慢问题攻坚

第八天核心任务&#xff1a;解决开发中的两大技术卡点今天的开发不仅聚焦于代码层面的数据库字段映射问题&#xff0c;还遭遇了一个困扰团队许久的环境难题 ——Go 项目启动异常缓慢。经过多维度排查&#xff0c;我们不仅理清了 GORM 命名策略的设计逻辑&#xff0c;还找到了影…

在Ubuntu上安装Google Chrome的详细教程

步骤 1&#xff1a;下载 Google Chrome 安装包 打开浏览器输入https://www.google.cn/chrome/&#xff0c;然后进入Chrome浏览器官方网站 点击下载选择Debian/Ubuntu版本 google-chrome-stable_current_amd64.deb步骤 2&#xff1a;安装下载的.deb 包 sudo dpkg -i google-chro…

el-table合并相同名称的行

el-table合并相同名称的行 <template><el-table:data"tableData":span-method"objectSpanMethod"border><el-table-columnprop"name"label"名称"width"180"></el-table-column><el-table-column…

解决 VSCode 无法从右键菜单“通过 Code 打开”文件夹的问题

&#x1f9e9; 一、问题现象 VSCode 已安装&#xff0c;但右键文件夹/桌面空白处无“通过 Code 打开在 VSCode 中执行 Shell Command: Install ‘Open with Code’ 无反应手动添加后菜单显示乱码&#xff08;如 €š‡ Code ‰“€&#xff09;点击右键菜单无响应或提示“找不到…

服务器数据恢复—服务器硬盘状态灯变红,分区数据恢复过程

服务器数据恢复环境&故障&#xff1a; 某公司服务器上有一组由3块硬盘组建的raid5磁盘阵列。 服务器上1块硬盘的状态灯变为红色&#xff0c;磁盘阵列出现故障&#xff0c;分区无法识别。服务器数据恢复过程&#xff1a; 1、将故障服务器上所有磁盘编号后取出。经过初检&…

MySQL → SQL → DDL → 表操作 → 数据类型 知识链整理成一份系统的内容

1. 知识结构MySQL└── SQL&#xff08;结构化查询语言&#xff09;├── DDL&#xff08;数据定义语言&#xff09; → 定义结构│ ├── 表操作&#xff08;创建/修改/删除表&#xff09;│ └── 数据类型&#xff08;列字段类型定义&#xff09;├── DML&…