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

Rust Tokio select宏中如何添加条件性Sleep分支?

问题:在Tokio select宏中实现条件性Sleep倒计时

我正在开发一款CLI游戏,使用异步select宏循环处理用户输入与事件间隔,希望在其中实现条件性Sleep倒计时功能。尝试声明一个Option<Sleep>,并在每次循环迭代时根据game.locking条件为其赋值,但代码无法运行。

尝试的代码

async fn run(game: &mut Game) {
    let mut reader = EventStream::new();

    draw().unwrap();

    let mut render_interval = interval(Duration::from_nanos(1_000_000_000 / TARGET_FRAME_RATE));
    let mut drop_interval = interval(Duration::from_millis(1_000 / game.level as u64));

    let mut lock_delay: Option<Sleep>;

    loop {
        lock_delay = if game.locking { Some(sleep(Duration::from_millis(500))) } else { None };

        select! {
            maybe_event = reader.next().fuse() => {
                match maybe_event {
                    Some(Ok(event)) => {
                        if let Event::Key(key) = event {
                            match key.code {
                                KeyCode::Char('w') | KeyCode::Char('W') | KeyCode::Up => {
                                    game.rotate(RotationDirection::Clockwise)
                                },
                                KeyCode::Char('a') | KeyCode::Char('A') | KeyCode::Left => {
                                    game.shift(ShiftDirection::Left)
                                },
                                KeyCode::Char('s') | KeyCode::Char('S') | KeyCode::Down => {
                                    game.soft_drop()
                                },
                                KeyCode::Char('d') | KeyCode::Char('D') | KeyCode::Right => {
                                    game.shift(ShiftDirection::Right)
                                },
                                KeyCode::Char(' ') => {
                                    game.hard_drop()
                                },
                                KeyCode::Char('z') | KeyCode::Char('Z') => {
                                    game.rotate(RotationDirection::CounterClockwise)
                                },
                                KeyCode::Char('c') | KeyCode::Char('C') => {
                                    game.hold()
                                },
                                KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
                                    break
                                },
                                _ => (),
                            }
                        }
                    },
                    Some(Err(error)) => panic!("{}", error),
                    None => (),
                }
            },
            _ = drop_interval.tick() => {
                game.shift(ShiftDirection::Down)
            },
            _ = lock_delay => {
                debug_println!("PLACING");
                game.place();
            },
            _ = render_interval.tick() => {
                render(game).unwrap()
            },
        };
    }
}

编译器报错

`Option<Sleep>` is not a future
the trait `futures::Future` is not implemented for `Option<Sleep>`
Option<Sleep> must be a future or must implement `IntoFuture` to be awaitedrustcClick for full compiler diagnostic

我还尝试过用Duration::from_secs(u64::MAX)替代None以避免使用Option,以及参考Reddit旧帖的方案,但均未成功。由于异步编程经验有限,恳请提供在Tokio select宏中实现条件性Sleep倒计时的可行方案。


解决方案

方法1:利用Tokio select的条件分支特性

这是最简洁的方案,直接借助Tokio select宏支持的条件后缀语法,仅当game.locking为true时,才将Sleep分支纳入监听范围:

async fn run(game: &mut Game) {
    let mut reader = EventStream::new();

    draw().unwrap();

    let mut render_interval = interval(Duration::from_nanos(1_000_000_000 / TARGET_FRAME_RATE));
    let mut drop_interval = interval(Duration::from_millis(1_000 / game.level as u64));

    loop {
        select! {
            maybe_event = reader.next().fuse() => {
                // 原输入处理逻辑不变
                match maybe_event {
                    Some(Ok(event)) => {
                        if let Event::Key(key) = event {
                            match key.code {
                                KeyCode::Char('w') | KeyCode::Char('W') | KeyCode::Up => {
                                    game.rotate(RotationDirection::Clockwise)
                                },
                                KeyCode::Char('a') | KeyCode::Char('A') | KeyCode::Left => {
                                    game.shift(ShiftDirection::Left)
                                },
                                KeyCode::Char('s') | KeyCode::Char('S') | KeyCode::Down => {
                                    game.soft_drop()
                                },
                                KeyCode::Char('d') | KeyCode::Char('D') | KeyCode::Right => {
                                    game.shift(ShiftDirection::Right)
                                },
                                KeyCode::Char(' ') => {
                                    game.hard_drop()
                                },
                                KeyCode::Char('z') | KeyCode::Char('Z') => {
                                    game.rotate(RotationDirection::CounterClockwise)
                                },
                                KeyCode::Char('c') | KeyCode::Char('C') => {
                                    game.hold()
                                },
                                KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
                                    break
                                },
                                _ => (),
                            }
                        }
                    },
                    Some(Err(error)) => panic!("{}", error),
                    None => (),
                }
            },
            _ = drop_interval.tick() => {
                game.shift(ShiftDirection::Down)
            },
            // 仅当game.locking为true时,才监听这个Sleep分支
            _ = sleep(Duration::from_millis(500)), if game.locking => {
                debug_println!("PLACING");
                game.place();
            },
            _ = render_interval.tick() => {
                render(game).unwrap()
            },
        };
    }
}

方法2:使用futures::future::Either统一类型

如果需要更灵活的分支控制,可以引入futures crate的Either类型,将Sleep和一个永远pending的future包装为统一类型:

首先在Cargo.toml中添加依赖:

futures = "0.3"

修改代码:

use futures::future::{Either, pending};
use tokio::time::{sleep, interval, Duration};

async fn run(game: &mut Game) {
    let mut reader = EventStream::new();

    draw().unwrap();

    let mut render_interval = interval(Duration::from_nanos(1_000_000_000 / TARGET_FRAME_RATE));
    let mut drop_interval = interval(Duration::from_millis(1_000 / game.level as u64));

    loop {
        // 根据条件选择Sleep或pending future(pending永远不会触发)
        let lock_delay = if game.locking {
            Either::Left(sleep(Duration::from_millis(500)))
        } else {
            Either::Right(pending())
        };

        select! {
            maybe_event = reader.next().fuse() => {
                // 原输入处理逻辑不变
                match maybe_event {
                    Some(Ok(event)) => {
                        if let Event::Key(key) = event {
                            match key.code {
                                KeyCode::Char('w') | KeyCode::Char('W') | KeyCode::Up => {
                                    game.rotate(RotationDirection::Clockwise)
                                },
                                KeyCode::Char('a') | KeyCode::Char('A') | KeyCode::Left => {
                                    game.shift(ShiftDirection::Left)
                                },
                                KeyCode::Char('s') | KeyCode::Char('S') | KeyCode::Down => {
                                    game.soft_drop()
                                },
                                KeyCode::Char('d') | KeyCode::Char('D') | KeyCode::Right => {
                                    game.shift(ShiftDirection::Right)
                                },
                                KeyCode::Char(' ') => {
                                    game.hard_drop()
                                },
                                KeyCode::Char('z') | KeyCode::Char('Z') => {
                                    game.rotate(RotationDirection::CounterClockwise)
                                },
                                KeyCode::Char('c') | KeyCode::Char('C') => {
                                    game.hold()
                                },
                                KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
                                    break
                                },
                                _ => (),
                            }
                        }
                    },
                    Some(Err(error)) => panic!("{}", error),
                    None => (),
                }
            },
            _ = drop_interval.tick() => {
                game.shift(ShiftDirection::Down)
            },
            _ = lock_delay => {
                // 额外判断确保只有条件满足时执行逻辑
                if game.locking {
                    debug_println!("PLACING");
                    game.place();
                }
            },
            _ = render_interval.tick() => {
                render(game).unwrap()
            },
        };
    }
}

关键说明

  • 方法1无需额外依赖,代码更简洁,每次循环都会重新创建Sleep future,保证game.locking变为true时倒计时从500ms重新开始。
  • 方法2适合需要更复杂分支逻辑的场景,通过统一类型让select宏能够正确识别分支。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 14:34:58