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

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

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 异步编程 嵌入式系统

推荐文章

Flet 构建跨平台应用的 Python 框架
2025-03-21 08:40:53 +0800 CST
Nginx 反向代理 Redis 服务
2024-11-19 09:41:21 +0800 CST
【SQL注入】关于GORM的SQL注入问题
2024-11-19 06:54:57 +0800 CST
赚点点任务系统
2024-11-19 02:17:29 +0800 CST
Python 微软邮箱 OAuth2 认证 Demo
2024-11-20 15:42:09 +0800 CST
如何在Vue3中处理全局状态管理?
2024-11-18 19:25:59 +0800 CST
前端如何给页面添加水印
2024-11-19 07:12:56 +0800 CST
10个几乎无人使用的罕见HTML标签
2024-11-18 21:44:46 +0800 CST
如何将TypeScript与Vue3结合使用
2024-11-19 01:47:20 +0800 CST
mysql删除重复数据
2024-11-19 03:19:52 +0800 CST
实用MySQL函数
2024-11-19 03:00:12 +0800 CST
使用 `nohup` 命令的概述及案例
2024-11-18 08:18:36 +0800 CST
curl错误代码表
2024-11-17 09:34:46 +0800 CST
如何在Vue 3中使用Ref访问DOM元素
2024-11-17 04:22:38 +0800 CST
在Rust项目中使用SQLite数据库
2024-11-19 08:48:00 +0800 CST
程序员茄子在线接单