rust嵌入式开发之await
嵌入式经常有类似通过串口发送指令然后等待响应再做出进一步反应的需求。比如,通过串口以AT命令来操作蓝牙模块执行扫描、连接,需要根据实际情况进行操作,复杂的可能需要执行7、8条指令才能完成连接。
对于这样的需求,如果用异步编程的方式来做,由于收发双方被解除了耦合,所以需要设置多种状态标记来对齐收发双方的步调,逻辑上非常不直观、操作上也非常繁琐,稍有疏漏就会出现bug,而且也不便于扩展【新功能需要重新修订状态空间,这就会对现有功能产生潜在的干扰】。所以我们一般都是将其串行化来简化编程。
在用c基于rt-thread开发时,这样的异步操作串行化我是通过系统提供的线程间通信工具【信号量】+用宏进行简写来实现的。
现在,当我们基于Embassy来开发嵌入式时,rust提供的async/await就是一套标准化的将并发操作进行串行化的工具。
理解await
rust中async/await的核心就是Future【一个需要系统介入的trait】:
1、async就是告诉编译器:我不是普通函数,我是一个Future,需要特殊对待
2、await则通知编译器,对于一个Future,需要按如下方式来操作:
- 为调用我,需要生成一个任务
- 将这个任务打包成一个可wake的上下文【当然还包括其它准备工作】
- 设置一个等待点,当任务执行完毕时会返回到该处并带回结果,然后从等待点继续执行
- 控制流转向这个任务,即启动这个任务,然后在其中执行我的poll函数【以该上下文为参数】
这个任务属于系统任务,由rust的运行时负责管理与调度。
我们完整描述一下await的异步操作串行化过程:
- 当前执行流启动异步操作后,发出await指令
- await指令会导致控制权被转移给rust的系统运行时
- 系统运行时启动一个用于检查该异步操作是否执行完毕的poll任务,这个poll任务将由系统按需调度
- poll任务每次被调度执行时都会检查异步操作的执行情况
- 检查后,如果通知异步操作还没执行完,系统就挂起poll任务并等待下一次调度机会
- 检查后,如果通知异步操作执行完毕了,系统就结束poll任务并带着异步操作的执行结果返回到await指令发出点
- 原来的执行流得到异步操作的执行结果,然后继续执行
大家可以看到,异步操作串行化需要完成大量的工作,在没有await的情况下,如我基于rtt的实现中,就全部需要我们自己来实现了。当然,我们自己的实现不需要考虑通用性,自然也就不会这么复杂,但主要的逻辑都是需要覆盖到的。
由我们自己编写的、负责检查异步任务是否完成的poll函数就两个返回值:
- Pending:指示异步操作还没有完成,则将poll任务丢进等待队列沉睡,等待wake后重新检查【即再次执行poll函数】
- Ready(data):指示异步操作执行完毕,则将poll任务从等待队列中踢出去,然后将Ready所包裹的数据作为结果返回调用任务时的等待点继续执行
现在就只有一个问题了:poll任务什么时候会被投入运行呢?!准备好的就可以执行。
那么,怎么才叫准备好的呢?!没执行过的、从沉睡中被wake的。
那么,怎么wake呢?!三种方式:
- 我们手动提取到waker,交给对应的系统部件,如放入时钟任务队列,由系统wake
- 我们手动提取到waker,自己保存起来,等条件满足时,我们自己wake
- 系统提取到waker,自动帮我们wake
就上述过程,Embassy/rust中存在两种特例:
1、Timer超时
如果我们需要原地等待上一段时间,我们一般会:
//等待10毫秒
Timer::after_millis(10).await;
这个时候并没有需要等待的异步操作,就单纯的等待时间的流逝。所以,就是把waker推入系统时钟队列,等待唤醒即可。
2、async标记的函数【rust中就是一个Future】
这个函数就是需要等待的异步操作。
这个时候,出现了三个执行流:
- 原执行流,由于await指令的调用,该执行流暂停
- 系统执行流,由于await指令的调用,系统运行时获得了执行权,其需要完成一系列的相关工作
- 异步执行流,系统运行时会创建一个新的任务,并在其中执行被async标记的异步函数,该函数什么时候会得到执行,理论上由系统运行时按需进行调度
当然,我们说的是理论上,不同的运行时自然有自己的考虑。
考虑到大家的习惯,我们一般都把异步操作封装到异步函数中来执行。但大家一定要理解:await是一个系统指令,和是否调用了async标记的函数没有必然的联系。
我们完全可以先创建一个Future,然后执行一系列准备工作并启动一个异步操作,然后await这个Future。而这,也正是await的标准使用方法,只不过我们经常会将执行准备工作和启动异步操作都放到一个函数中来做,最后由这个函数返回一个Future。所以大家才形成了async/await配对使用的印象。
但这个印象当然是错误的:只要是Future就可以await,async只不过简化了【创建一个匿名struct、为其编写poll函数及Future、将异步函数封装进一个隐式函数中完成创建该匿名struct、调用函数、返回刚创建的匿名struct】这一繁琐的过程而已。
作为运行时的Embassy
Embassy实现了一个任务执行器,rust编译器会自动将其装配成系统运行时,我们写好自己的Future,然后在需要时await即可,rust编译器会配合Embassy的任务执行器帮助我们自动完成上面罗列的一大堆工作。
Embassy还实现了时钟任务队列,我们可以借用来实现超时。但需要我们显式导入,即首先需要在Cargo.toml中声明对应的依赖:
[dependencies]
...其它依赖
embassy-time-queue-driver = "0.1.0"
然后在我们的程序中使用:
let waker = cx.waker();
embassy_time_queue_driver::schedule_wake(self.expires_at.as_ticks(), waker);
举个例子
我们的目标是实现:串口发送命令后等待如下响应
- 接收正确
- 接收错误
- 接收超时【未得到任何响应】
注:何为正确、何为错误,这属于业务上的判断,对我们这个例子来说,不需要关心
我们需要为这个响应设计一个数据结构:
#[derive(Clone)]
pub struct ExpectResult {//超时检测的最后期限expires_at: Instant,//响应处理完毕的标记ready: bool,//负责叫醒任务的服务员waker: Option<core::task::Waker>,//区分响应状态的一个结果码枚举rc: ExpectResultCode,//执行结果,根据我们的业务来设计,各种形式都可以result: (...我们需要的数据...),
}
注:考虑rust的借用,一般需要抽离成一个内部的struct来做Future,外部只做包裹以及必要的操作接口。但为便于说明,这一部分就略去了
一般来说,作为基础结构,我们都会用泛型实现通用代码。通用代码应该包括两块:结果指示码、执行结果。其它语言一般都只能把结果指示码放入执行结果、在ExpectResult中用单独的timeout指示码指示是否超时,但rust则可以将其合并到结果指示码中,这样的话,处理时就可以统一处理:接收正确、接收错误、超时了。业务逻辑上更顺畅。
结果码我们可以如下定义:
#[derive(Clone, Copy)]
pub enum ExpectResultCode<T: Copy> {None,//框架不关心的业务码BPCode(T),//框架需要处理的超时,但也是业务层面的超时Timeout,
}
通过rust强大的枚举,原本由框架管理的Timeout就依然和BPCode一样具有业务层级的语义,但又不受业务代码的干扰。
然后我们可以根据我们的业务需要为其准备各种操作,但其中应该有两个:
impl ExpectResult {...其它业务代码...//代替new来创建期望结果的数据结构,如果不需要超时,就直接用newpub fn timeout(duration: Duration) -> Self {Self {expires_at: Instant::now() + duration,ready: false,waker: None,rc: ExpectResultCode::None,result: (...现在还没有结果,应该就是一堆None/0之类...),}}//异步操作完毕,设置结果;TC是业务结果码的泛型pub fn set_result(&mut self, rc: TC, result: ...) {self.rc = ExpectResultCode::BPCode(rc);self.result = ...;//异步操作结束self.ready = true;//唤醒等待中的poll任务if let Some(waker) = &self.waker {waker.wake();}}
}
然后我们就可以来实现Future了:
impl Unpin for ExpectResult {}
impl Future for ExpectResult {type Output = (ExpectResultCode, ...我们需要的结果类型...);fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {if self.ready {//异步操作执行完毕,即有人执行了set_resultPoll::Ready((self.rc, ...set_result函数送入的结果...))} else if self.expires_at < Instant::now() {//超时Poll::Ready((ExpectResultCode::Timeout, ...结果自然是没有了...))} else {//第一次调用,为上两个分支准备wakerlet waker: &core::task::Waker = cx.waker();//为set_result准备唤醒服务self.waker = Some(waker.clone());//为超时准备唤醒服务embassy_time_queue_driver::schedule_wake(self.expires_at.as_ticks(), waker);//最后,告诉系统:我先洗洗睡了,好了叫我Poll::Pending}}
}
ok,那该怎么用呢?!
//以各种方式创建一个ExpectResult并为其按业务需要装订数据
//如等待50毫秒
let es = ExpectResult::timeout(Duration::from_millis(50));
...其它操作,如通过串口发送命令...//准备完毕,异步操作已经启动,开始等待响应结果
//如你所见,对的,await针对的就是一个struct,而不是函数。
//啊?!可async修饰的就是一个函数啊?!不是async和await配合使用吗?!
//我们的ExpectResult已经在上面实现了Future,所以它可以直接用await进行串行化
//而如果是async函数,rust编译器会好心的给我们准备一个匿名struct的来实现我上面讲的东东的
//然后把async所修饰的函数作为需要等待执行完毕的异步操作丢进另一个线程/协程中执行
//并在该异步函数执行完毕时自动帮我们wake并返回该异步函数执行的结果
let (rc, ...我们需要的结果...) = es.await;
match rc {ExpectResultCode::Timeout => {//超时处理,如重发三次等等...},...
}
相比我们自己的实现,await机制,由于系统运行时和编译器配合着帮我们做了相当多的工作,自然非常轻便与灵活,而且编写起来也比较简单、逻辑清晰。
相关文章:
rust嵌入式开发之await
嵌入式经常有类似通过串口发送指令然后等待响应再做出进一步反应的需求。比如,通过串口以AT命令来操作蓝牙模块执行扫描、连接,需要根据实际情况进行操作,复杂的可能需要执行7、8条指令才能完成连接。 对于这样的需求,如果用异步…...
UE4_碰撞_碰撞蓝图节点——Line Trace For Objects(对象的线条检测)
一、Line Trace For Objects(对象的线条检测):沿给定线条执行碰撞检测并返回遭遇的首个命中,这只会找到由Object types指定类型的对象。注意他与Line Trace By Channel(由通道检测线条)的区别,一个通过Obje…...
抽象类和接口的简单认识
目录 一、抽象类 1.什么是抽象类 2.抽象类的注意事项 3.抽象类与普通类的对比 二、接口 1.接口的简单使用 2.接口的特性 3.接口的使用案例 4.接口和抽象类的异同 一、抽象类 所谓抽象类,就是更加抽象的类,也就是说,这个类不能具体描…...
python-pytorch获取FashionMNIST实际图片标签数据集
在查看pytorch官方文档的时候,在这里链接中https://pytorch.org/tutorials/beginner/basics/data_tutorial.html的Creating a Custom Dataset for your files章节,有提到要自定义数据集,需要用到实际的图片和标签。 在网上找了半天没找到&a…...
深入探秘Python生成器:揭开神秘的面纱
一、问题起源: 想象一下,您掌握了一种魔法,在代码世界里,您可以轻松呼唤出一个整数。然而,事情并不总是看起来那样简单。在Python的奇妙王国中,我遇到了一个有趣的谜题: def tst():try:print(…...
红队攻防渗透技术实战流程:红队目标信息收集之批量信息收集
红队资产信息收集 1. 自动化信息收集1.1 自动化信息收集工具1.2 自动域名转换IP工具1.3 自动企业信息查询工具1.4 APP敏感信息扫描工具1.5 自动化信息工具的使用1.5.1 资产灯塔系统(ARL)1.5.1.1 docker环境安装1.2.2.9.1 水泽-信息收集自动化工具1. 自动化信息收集 1.1 自动化…...
【vue3学习笔记(二)】(第141-143节)初识setup;ref函数_处理基本类型;ref函数_处理对象类型
尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 本篇内容对应课程第141-143节 课程 P141节 《初识setup》笔记 1、setup是所有组合式API“表演的舞台”,组件中所用到的所有数据、方法、监视数据、生命周期钩子等都需要配置在setup中。 2、setup的两种返回值&…...
若依框架学习使用
若依官网项目拉取下来介绍 | RuoYi 项目运行: 1.idea安装,可以运行前后端 编辑器idea、jdk环境安装、数据库mysql、navicat工具、redis(redis-server启动)安装 2.navicat数据库连接, 创建数据库ry-vue并导入数据脚本ry_2021xxxx.sql,qua…...
蓝桥杯_数学模板
1.试除法判定质数 #include <iostream> using namespace std;bool is_zs(int x) {if(x<2) return false;for(int i2;i<x/i;i)if(x%i0)return false;return true; }int main() {int n; cin>>n;while(n--){int x; cin>>x;if(is_zs(x)) cout<<&quo…...
稀碎从零算法笔记Day31-LeetCode:接雨水
半月一去,望舒一轮,明天开始攻坚哈德题了 前言:非常经典的一道笔试题,看了保证血赚(今天银泰星笔试第四题就是这个) 题型:dp、模拟、双指针…… 链接:42. 接雨水 - 力扣ÿ…...
微前端的使用和注意事项 - qiankun
一、为什么使用微前端 微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。微前端的核心目标是将巨石应用拆解成…...
uniapp微信小程序消息订阅详解
一、微信公众平台申请订阅模板 注意:订阅信息 这个事件 是 当用户 点击的时候触发 或者 是 支付成功后触发, 用户勾选 “总是保持以上选择,不再询问” 之后或长期订阅,下次订阅调用 wx.requestSubscribeMessage 不会弹窗…...
git 查看文件夹结构树
在Git中,没有直接的命令可以像文件系统那样展示一个可视化的文件结构树。但是,你可以使用一些外部工具或命令来达到这个目的。 以下是一些方法,你可以使用它们来查看Git仓库的文件结构树: 使用tree命令(如果你的系统已…...
设计模式一详解
一、观察者模式 当一个对象状态发生改变时,依赖它的对象全部会收到通知,并自动更新 场景:一个事件发生后,要执行一连串更新操作。传统的编程方式,就是在事件的代码之后直接加入处理逻辑。当更新的逻辑增多之后&#x…...
python 进程、线程、协程基本使用
1、进程、线程以及协程【1】进程概念【2】线程的概念线程的生命周期进程与线程的区别 【3】协程(Coroutines) 2、多线程实现【1】threading模块【2】互斥锁【3】线程池【4】线程应用 3、多进程实现4、协程实现【1】yield与协程【2】asyncio模块【3】3.8版本【4】aiohttp 1. 并发…...
SQLite3进行数据库各项常用操作
目录 前言1、SQLite介绍2、通过SQLite创建一个数据库文件3、往数据库文件中插入数据4、数据库文件信息查询5、修改数据库中的内容6、删除数据库中的内容 前言 本文是通过轻量化数据库管理工具SQLite进行的基础操作和一些功能实现。 1、SQLite介绍 SQLite是一个广泛使用的嵌入…...
Debian GNU/Linux 安装docker与docker compose
安装 Docker 更新包列表 sudo apt update 安装必要的软件包,以便让 APT 可以通过 HTTPS 使用存储库: sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common 添加 Docker 的官方 GPG 密钥: cu…...
图片标注编辑平台搭建系列教程(2)——fabric.js简介
文章目录 综述数据管理图形渲染图形编辑事件监听预告 综述 fabric提供了二维图形编辑需要的所有基础能力,包括:数据管理、图形渲染、图形编辑和事件监听。其中,图形编辑可以通过事件监听和图形渲染来实现,所以可以弃用。数据管理…...
Debian linux版本下运行的openmediavault网盘 千兆网卡升级万兆
一、适用场景 1、使用vmware ESXi虚拟化平台运行多种不同应用服务器时,其中网盘服务器采用开源的openmediavault搭建; 2、将老专业服务器升级千兆网为万兆网; 3、需要转移的数据量大的企业或用户; 4、从服务器到服务器的数据转移…...
前端 CSS 经典:grid 栅格布局
前言:Grid 布局是将容器划分成"行"和"列",产生单元格,然后将"项目"分配给划分好的单元格,因为有行和列,可以看作是二维布局。 一 术语 1. 容器 采用网格布局的区域,也就是…...
多输入多输出通道
文章目录 图像卷积填充和步幅填充步幅 多输入多输出通道1x1卷积层 图像卷积 卷积原理: 就是将之前的大的图片,定义一个核函数,然后经过移动并运算将图片变小了.也就是将图像压缩提取整合特征值. 这里利用的时乘法. 填充和步幅 填充 在应用多层卷积时,我们常常…...
http响应练习—在服务器端渲染html(SSR)
一、什么是服务器端渲染(SSR) 简单说,就是在服务器上把网页生成好,整个的HTML页面生成出来,生成出的页面已经包含了所有必要的数据和结构信息,然后直接发给浏览器进行展现。 二、例题 要求搭建http服务&a…...
C++(8): std::deque的使用
1. std::deque std::deque 是 C 标准库中的一个双端队列容器。这个容器支持在序列的两端进行快速的插入和删除操作,其时间复杂度为常数时间 O(1)。同时,std::deque 也提供了对序列中任意元素的随机访问。 2. 特点 (1)双端操作&…...
openwrt开发包含路由器基本功能的web问题记录
1.这里的扫描怎么实现的先找一些luci代码,在openwrt21版本后,luci用js替换了lua写后台,先找一些代码路径 在openrwt15这部分代码是在这个目录下 feeds/luci/modules/luci-mod-admin-full/luasrc/view/admin_network/wifi_join.htm 里面包含…...
HarmonyOS ArkTS 骨架屏加载显示(二十五)
目录 前言1、骨架屏代码显示2、代码中引用3、效果图展示 前言 所谓骨架屏,就是在页面进行耗时加载时,先展示的等待 UI, 以告知用户程序目前正在运行,稍等即可。 等待的UI大部分是 loading 转圈的弹窗,有的是自己风格的小动画。其实…...
Ruoyi-Cloud-Plus_使用Docker部署分布式微服务系统_环境准备_001---SpringCloud工作笔记200
1.首先安装docker: 如果以前安装过首先执行: yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine 去卸载docker 2.安装dokcer需要的工具包…...
RN封装的底部向上弹出的弹出层组件
组件代码 import React from react; import { View, StyleSheet, Modal, TouchableOpacity, Text, TouchableWithoutFeedback } from react-native;const BottomPopup ({ visible, onClose, children, leftButtonTitle, rightButtonTitle, onLeftButtonPress, onRightButtonP…...
基于深度学习YOLOv8+PyQt5的水底海底垃圾生物探测器检测识别系统(源码+数据集+配置说明)
wx供重浩:创享日记 对话框发送:323海底 获取完整源码7000张数据集配置说明文件说明远程操作配置环境跑通程序 效果展示 基于深度学习YOLOv8PyQt5的水底海底垃圾生物探测器检测识别系统设计(源码数据集配置文件) 各文件说明 程序运…...
SpringBoot集成WebSocket实现简单的多人聊天室
上代码—gitee下载地址: https://gitee.com/bestwater/Spring-websocket.git下载代码,连上数据库执行SQL,就可以运行,最终效果...
如何使用固定公网地址远程访问内网Axure RP生成的网站原型web页面
文章目录 前言1.在AxureRP中生成HTML文件2.配置IIS服务3.添加防火墙安全策略4.使用cpolar内网穿透实现公网访问4.1 登录cpolar web ui管理界面4.2 启动website隧道4.3 获取公网URL地址4.4. 公网远程访问内网web站点4.5 配置固定二级子域名公网访问内网web站点4.5.1创建一条固定…...
做网站360推广多少钱/潍坊网站定制模板建站
前面有个return下面的txtBox1.Focus();当然无法访问了。应该把return放到txtBox1.Focus()后面...
建设一个网站app需要多少钱/一个新产品的营销方案
0效果 1来由 首先我有个程序需要用到进度条,我首先试了一下MATLAB自带的进度条: barwaitbar(0,读取数据中...); % waitbar显示进度条 for i1:1000A(i)rand();str[计算中...,num2str(100*i/1000),%]; % 显示的文本waitbar(i/1000,bar,str) …...
图片生成器软件/淘宝关键词优化技巧教程
在一般的网页中,都会有登录功能,只有登录的用户才能有权限访问系统中的资源。一般检查用户是否登录通常是通过跟踪用户的session完成的,通过ActionContext既可以访问到session中的属性,拦截器的intercept(ActionInvocation invoca…...
优速网站建设工作室/短网址在线生成
在上集中,我们已经构建了一个简单的待办列表应用(to-do list app),这个应用可以在待办项过期时通过本地通知提醒用户。现在,我们要在之前的基础上添加以下功能:应用图标角标上显示过期待办项的数量、通知动…...
乌鲁木齐疫情最新消息/站长工具seo查询软件
上文中主要介绍了Kafka 的消费位移从Zookeeper 转移到了自己管理。本文主要介绍一下位移的提交方式。 Consumer 需要向 Kafka 汇报自己的位移数据,这个汇报过程被称为提交位移。因为 Consumer 能够同时消费多个分区的数据,所以位移的提交实际上是在分区粒…...
做区块链在哪个网站/培训报名
在实际开发中,项目经理会一直强调一句话,永远不要相信客户端的数据(前端可以不用验证,但是后端必须验证)。大家同意这样的说法吧。。新端验证毋庸质疑JS验证,提高用户体验我们不得不添加一些与后端一致的验证逻辑,同样…...