You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决用lilos并行调用Rust结构体异步方法的可变借用问题?

解决Rust+Lilos嵌入式系统中异步并发的可变self借用冲突问题

问题背景

基于Rust和lilos crate开发小型嵌入式系统时,想要异步并发运行对象的两个方法,但由于两个方法都需要可变借用self,导致无法同时调用。需求是实现LED的两个功能:

  • 异步闪烁LED
  • 接收命令控制LED是否激活

当前代码如下:

use core::convert::Infallible;
use embedded_hal::digital::v1::OutputPin;

use futures::select_biased;
use futures::FutureExt;
use lilos;
use lilos::exec::PeriodicGate;
use thingbuf as conn;
pub struct Led<'a> {
    pin: &'a mut  dyn OutputPin,
    gate: PeriodicGate,
    recv_cmd: conn::mpsc::Receiver<bool>,
    send_cmd: conn::mpsc::Sender<bool>,
    active: bool,
}
impl<'a> Led<'a> {
    pub fn new(pin: &'a mut dyn OutputPin ) -> Self {
        let (send_cmd, recv_cmd) = conn::mpsc::channel::<bool>(10);
        let gate = PeriodicGate::from(lilos::time::Millis(500));
        Self {
            pin,
            gate,
            send_cmd,
            recv_cmd,
            active: true,
        }
    }

    async fn cmd(&mut self, v: bool) {
        let _ = self.send_cmd.send(v).await;
    }

    async fn blink(&mut self) -> Infallible {
        loop {
            self.gate.next_time().await;
            let _ = self.pin.set_high();
            self.gate.next_time().await;
            let _ = self.pin.set_low();
        }
    }
    async fn on_cmd(&mut self) -> Infallible {
        loop {
            let cmd = self.recv_cmd.recv().await;
            if let Some(x) = cmd {
                self.active = x;
            }
        }
    }
    pub async fn run(&mut self) -> Infallible {
            select_biased!(
                _ = self.blink().fuse() => {},
                _ = self.on_cmd().fuse() => {}
            )
    }
}

尝试过Arc、RefCell、Mutex但编译失败,仍存在借用冲突问题。

解决方案

由于lilos是单线程异步执行器,我们可以利用Rust的内部可变性(RefCell)安全地共享状态,同时拆分任务对self的依赖,避免同时借用整个可变self。

步骤1:重构结构体,分离共享状态

将需要跨任务共享的active标志用RefCell包裹,同时把命令发送器暴露给外部(移除内部冗余的cmd方法):

use core::cell::RefCell;
use core::convert::Infallible;
use embedded_hal::digital::v1::OutputPin;

use futures::select_biased;
use futures::FutureExt;
use lilos;
use lilos::exec::PeriodicGate;
use thingbuf as conn;

pub struct Led<'a> {
    pin: &'a mut dyn OutputPin,
    gate: PeriodicGate,
    recv_cmd: conn::mpsc::Receiver<bool>,
    active: RefCell<bool>, // 单线程下用RefCell实现安全的内部可变访问
}

// 暴露给外部的命令发送器类型
pub type LedCmdSender = conn::mpsc::Sender<bool>;

步骤2:调整构造函数,返回发送器

构造函数返回Led实例和命令发送器,让外部可以直接发送控制命令:

impl<'a> Led<'a> {
    pub fn new(pin: &'a mut dyn OutputPin) -> (Self, LedCmdSender) {
        let (send_cmd, recv_cmd) = conn::mpsc::channel::<bool>(10);
        let gate = PeriodicGate::from(lilos::time::Millis(500));
        let led = Self {
            pin,
            gate,
            recv_cmd,
            active: RefCell::new(true),
        };
        (led, send_cmd)
    }

步骤3:修改任务方法,避免整体可变借用

让blink和on_cmd分别持有所需的部分引用,而非整个&mut self。单线程下RefCell的访问不会产生运行时panic:

// blink任务:仅依赖引脚、周期门和激活状态
    async fn blink(pin: &mut dyn OutputPin, mut gate: PeriodicGate, active: &RefCell<bool>) -> Infallible {
        loop {
            gate.next_time().await;
            if *active.borrow() {
                let _ = pin.set_high();
            }
            gate.next_time().await;
            if *active.borrow() {
                let _ = pin.set_low();
            }
        }
    }

    // on_cmd任务:仅依赖命令接收器和激活状态
    async fn on_cmd(mut recv_cmd: conn::mpsc::Receiver<bool>, active: &RefCell<bool>) -> Infallible {
        loop {
            if let Some(cmd) = recv_cmd.recv().await {
                *active.borrow_mut() = cmd;
            }
        }
    }

步骤4:修改run方法,拆分任务参数

将self的成员拆分后传递给两个任务,彻底避免同时借用整个可变self:

pub async fn run(mut self) -> Infallible {
        let active_ref = &self.active;
        select_biased!(
            _ = Self::blink(&mut self.pin, self.gate, active_ref).fuse() => {},
            _ = Self::on_cmd(self.recv_cmd, active_ref).fuse() => {}
        )
    }
}

外部使用示例

// 初始化LED和命令发送器
let (mut led, cmd_sender) = Led::new(&mut led_pin);

// 在其他任务中发送控制命令
lilos::exec::spawn(async move {
    // 3秒后关闭LED
    lilos::time::sleep(lilos::time::Millis(3000)).await;
    let _ = cmd_sender.send(false).await;
});

// 运行LED主任务
led.run().await;

关键说明

  1. 单线程内部可变性:lilos是单线程执行器,RefCell完全安全,无需使用多线程的Mutex,避免不必要的开销。
  2. 拆分任务依赖:通过将任务需要的参数单独传递,而非持有整个self的可变引用,从根源上解决了借用冲突。
  3. 移除冗余逻辑:原代码中Led内部持有send/receiver是不必要的,将sender暴露给外部,符合单一职责原则。

内容的提问来源于stack exchange,提问作者Lieven

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 02:17:18