【问题标题】:Rust tokio infinite loop for multiple buttons listening用于多个按钮监听的 Rust tokio 无限循环
【发布时间】:2020-06-15 23:02:43
【问题描述】:

我是 Rust 的新手,我很难理解如何在无限循环中使用 tokio 和阻塞代码。我有两个按钮连接到我的 Raspberry Pi 3,我想听 either 按钮被按下。我正在为此使用rust_gpiozero crate。

这里是按钮代码:

use rust_gpiozero::*;
let mut button = Button::new(19);
button.wait_for_press(None); // blocking here

我不知道如何持续监听主代码中的任一按钮。我想我应该使用tokio::task::spawn_blocking,但我不确定如何使用。像这样的:

#[tokio::main]
async fn main() {
  let b1_blocking_task = tokio::task::spawn_blocking(|| {
    let mut button = Button::new(19);
    button.wait_for_press(None); // blocks here
  });

  let b2_blocking_task = tokio::task::spawn_blocking(|| {
    let mut button = Button::new(26);
    button.wait_for_press(None); // blocks here
  });

  loop { // forever listen for button presses
     tokio::select! {
       _ = b1_blocking_task => {
           println!("Button 1 pressed")
       }
       _ = b2_blocking_task => {
           println!("Button 2 pressed")
       }
     };
  }
}

上面的代码不起作用,但是如何正确执行此操作的最佳策略是什么?

【问题讨论】:

  • 在生成的任务中使用通道和循环。
  • 嗨 Stargateur,感谢您的回复。我不确定我是否遵循,你的意思是把每个 wait_for_press 函数放在自己的循环中{},然后再进行频道发送调用?

标签: rust rust-tokio


【解决方案1】:

对您的代码稍作改动即可使其工作:

#[tokio::main]
async fn main() {
    let b1_blocking_task = || {
        tokio::task::spawn_blocking(|| {
            let mut button = Button::new(19);
            button.wait_for_press(None); // blocks here
        })
    };

    let b2_blocking_task = || {
        tokio::task::spawn_blocking(|| {
            let mut button = Button::new(26);
            button.wait_for_press(None); // blocks here
        })
    };

    loop {
        // forever listen for button presses
        tokio::select! {
          _ = b1_blocking_task() => {
              println!("Button 1 pressed")
          }
          _ = b2_blocking_task() => {
              println!("Button 2 pressed")
          }
        };
    }
}

这将为每次按下按钮生成一个新线程。它不是特别有效,但如果这些是用户输入事件,也可能不太重要。

您可以通过只生成每个线程一次并使用通道在它们之间进行通信来使其变得更好:

use tokio::sync::mpsc::channel;

#[tokio::main]
async fn main() {
    // pick suitable queue sizes for these channels
    let (mut b1_sender, mut b1_receiver) = channel(16);
    let (mut b2_sender, mut b2_receiver) = channel(16);

    let b1_blocking_task = tokio::task::spawn_blocking(|| {
        let mut button = Button::new(19);
        button.wait_for_press(None); // blocks here
        // will panic if the channel queue gets full
        b1_sender.try_send(()).unwrap();
    });

    let b2_blocking_task = tokio::task::spawn_blocking(|| {
        let mut button = Button::new(26);
        button.wait_for_press(None); // blocks here
        // will panic if the channel queue gets full
        b2_sender.try_send(()).unwrap();
    });

    let (_, _, _) = tokio::join!(
        b1_blocking_task,
        b2_blocking_task,
        tokio::task::spawn(async move {
            loop {
                // forever listen for button presses
                tokio::select! {
                    Some(_) = b1_receiver.recv() => {
                        println!("Button 1 pressed")
                    }
                    Some(_) = b2_receiver.recv() => {
                        println!("Button 2 pressed")
                    }
                    else => break
                };
            }
        })
    );
}

您使用的 API 并不理想,因为它要求您创建阻塞线程。如果有一些方法可以轮询按钮以查看它是否已被单击,那会好得多。在内部,板条箱似乎正在这样做,因此它应该可以公开机制,这将使您进一步改进。

【讨论】:

  • 谢谢彼得,我怀疑箱子的设计有点奇怪,但由于我缺乏 Rust 经验,我不确定。我会寻找更好的东西。仅供参考,由于按钮的多次初始化,代码会出现恐慌,例如PinNotAvailable(19),但该策略似乎有效。
  • 当我在我的 Pi 3 上运行它时,第二个 sn-p 代码会连续打印 Button 1 pressedButton 2 pressed(无需我按下任何一个按钮)。
  • 线程可能恐慌,因此未来已经准备好并报告无。我不确定使用 try_send 是否有意义。
猜你喜欢
  • 2016-08-16
  • 2017-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多