编程 Rust 异步 —— 让嵌入式编程更加简单

2024-11-18 03:21:42 +0800 CST views 598

Rust 异步 —— 让嵌入式编程更加简单

Futures 在 Rust 中用于异步编程,类似于 JavaScript 的 promise 原理,两者都是 async/await 语句的基础。用户可以使用它们以串行编程的方式实现异步功能。

Futures 在标准的 std 和嵌入式的 nostd 环境中都有支持,使用方式一致。在 std 环境中,比较著名的异步平台是 Tokio,而在嵌入式领域,embassy 提供了一个高效的异步平台。

到底什么是 Future?

简单来说,Future 用于表示一些异步计算的值,也就是说这些值无法在当前立即得出,但后续操作又需要依赖这些值。举个例子,在嵌入式编程中,处理串口接收和数据处理时,传统的串行编程方式如下:

void loop() {
    char ch;
    if (ch == serial.read()) {
        switch (ch) {
            case 'A': do_something(); break;
            case 'B': do_something_else(); break;
            default: break;
        }
    }
    do_something();
}

在这个简单的例子中,逻辑非常清晰,但 CPU 的执行效率却很低,因为它会阻塞在串口的读操作上。更有经验的程序员可能会使用中断或操作系统来提高效率,但这需要小心处理多线程或中断带来的其他风险,同时代码的可读性也会下降。

如果使用 Rust 中的 Future 替代这个程序,则可以如下实现:

async fn loop() {
    let ch = serial.read().await;
    match ch {
        'A' => do_something(),
        'B' => do_something_else(),
        _ => do_some(),
    }
    do_something();
}

在异步 Rust 代码中,依旧保持了串行编程的模式,但 CPU 或线程不会阻塞在 read() 操作上,而是在后台等待数据来临时自动唤醒,极大地提高了执行效率。

Future 的实现原理

在大多数等待任务完成的场景下,系统后台需要一个执行器(executor),通过事件唤醒 Future 来重新激活 await 语句。这个执行器对任务实现了任务和激活机制的抽象,它不会反复查询事件信号,而是被动等待事件的唤醒。Future 的简单模型如下:

use std::cell::RefCell;

thread_local!(static NOTIFY: RefCell<bool> = RefCell::new(true));

struct Context<'a> {
    waker: &'a Waker,
}

impl<'a> Context<'a> {
    fn from_waker(waker: &'a Waker) -> Self {
        Context { waker }
    }

    fn waker(&self) -> &'a Waker {
        &self.waker
    }
}

struct Waker;

impl Waker {
    fn wake(&self) {
        NOTIFY.with(|f| *f.borrow_mut() = true)
    }
}

enum Poll<T> {
    Ready(T),
    Pending,
}

trait Future {
    type Output;

    fn poll(&mut self, cx: &Context) -> Poll<Self::Output>;
}

#[derive(Default)]
struct MyFuture {
    count: u32,
}

impl Future for MyFuture {
    type Output = i32;

    fn poll(&mut self, ctx: &Context) -> Poll<Self::Output> {
        match self.count {
            3 => Poll::Ready(3),
            _ => {
                self.count += 1;
                ctx.waker().wake();
                Poll::Pending
            }
        }
    }
}

fn run<F>(mut f: F) -> F::Output
where
    F: Future,
{
    NOTIFY.with(|n| loop {
        if *n.borrow() {
            *n.borrow_mut() = false;
            let ctx = Context::from_waker(&Waker);
            if let Poll::Ready(val) = f.poll(&ctx) {
                return val;
            }
        }
    })
}

fn main() {
    let my_future = MyFuture::default();
    println!("Output: {}", run(my_future));  // 输出:Output: 3
}

上述代码展示了一个简单的 Future 调度器 run 函数,它不断检查任务的状态,并通过 Waker 唤醒。最终的任务结果通过 Poll::Ready 返回。

使用 embassy 异步处理串口数据

Rust 的 embassy 提供了一个简单的异步嵌入式编程框架,使用它可以轻松处理嵌入式设备中的异步任务。以下是 embassy 中处理串口数据的一个例子:

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_nrf::init(Default::default());
    let mut config = uarte::Config::default();
    config.parity = uarte::Parity::EXCLUDED;
    config.baudrate = uarte::Baudrate::BAUD115200;

    let mut uart = uarte::Uarte::new(p.UARTE0, Irqs, p.P0_08, p.P0_06, config);
    info!("uarte initialized!");

    let mut buf = [0; 8];
    buf.copy_from_slice(b"Hello!\r\n");

    unwrap!(uart.write(&buf).await);
    info!("wrote hello in uart!");

    loop {
        info!("reading...");
        unwrap!(uart.read(&mut buf).await);
        info!("writing...");
        unwrap!(uart.write(&buf).await);
    }
}

最后

使用 Rust 的 Future 进行异步编程几乎没有额外的成本。它不会生成多余的状态逻辑代码,同时代码的可读性也依旧很高。如果你有兴趣深入学习,可以阅读以下书籍:

复制全文 生成海报 编程 Rust 异步编程 嵌入式系统

推荐文章

Vue3中如何实现插件?
2024-11-18 04:27:04 +0800 CST
JavaScript设计模式:装饰器模式
2024-11-19 06:05:51 +0800 CST
Java环境中使用Elasticsearch
2024-11-18 22:46:32 +0800 CST
php腾讯云发送短信
2024-11-18 13:50:11 +0800 CST
介绍25个常用的正则表达式
2024-11-18 12:43:00 +0800 CST
MyLib5,一个Python中非常有用的库
2024-11-18 12:50:13 +0800 CST
OpenCV 检测与跟踪移动物体
2024-11-18 15:27:01 +0800 CST
免费常用API接口分享
2024-11-19 09:25:07 +0800 CST
如何在 Vue 3 中使用 Vuex 4?
2024-11-17 04:57:52 +0800 CST
一个简单的html卡片元素代码
2024-11-18 18:14:27 +0800 CST
html一个全屏背景视频
2024-11-18 00:48:20 +0800 CST
windows下mysql使用source导入数据
2024-11-17 05:03:50 +0800 CST
Vue中的表单处理有哪几种方式?
2024-11-18 01:32:42 +0800 CST
初学者的 Rust Web 开发指南
2024-11-18 10:51:35 +0800 CST
PHP 允许跨域的终极解决办法
2024-11-19 08:12:52 +0800 CST
回到上次阅读位置技术实践
2025-04-19 09:47:31 +0800 CST
使用Python提取图片中的GPS信息
2024-11-18 13:46:22 +0800 CST
PHP设计模式:单例模式
2024-11-18 18:31:43 +0800 CST
20个超实用的CSS动画库
2024-11-18 07:23:12 +0800 CST
Go 协程上下文切换的代价
2024-11-19 09:32:28 +0800 CST
Vue3中的虚拟滚动有哪些改进?
2024-11-18 23:58:18 +0800 CST
windon安装beego框架记录
2024-11-19 09:55:33 +0800 CST
Python 微软邮箱 OAuth2 认证 Demo
2024-11-20 15:42:09 +0800 CST
程序员茄子在线接单