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

如何实现定时器线程的退出、暂停与恢复?——可暂停番茄钟开发疑问

解决方案:非阻塞定时器与线程控制

核心问题分析

当前实现的主要问题是主线程依赖thread::sleep(1秒)进行阻塞等待,导致输入事件只能在每秒一次的定时器循环间隙被处理,无法及时响应按键操作。此外,缺少对定时器生命周期的暂停/退出控制机制。

改进方案

1. 替换阻塞睡眠为非阻塞时间跟踪

使用Instant记录上次更新定时器的时间,在主循环中以较短间隔(如50ms)轮询输入事件,同时判断是否需要更新剩余时间。既保证输入响应及时,又能准确计时。

2. 引入共享状态控制定时器行为

通过Arc<Mutex<TimerState>>实现线程间的状态共享,输入线程可触发暂停/继续/退出操作,主线程根据状态调整执行逻辑。

修改后的完整代码示例

use crossterm::{
    event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
    terminal::{disable_raw_mode, enable_raw_mode, Terminal},
    ExecutableCommand,
};
use std::{
    sync::{mpsc, Arc, Mutex},
    thread,
    time::{Duration, Instant},
};

// 定义定时器状态枚举
#[derive(Clone, Copy, PartialEq)]
enum TimerState {
    Running,
    Paused,
    Exited,
}

// 输入事件类型
enum CliEvent {
    Input(Event),
    Tick,
}

// 处理输入后的返回类型
enum InputEvent {
    Continue,
    Quit,
    TogglePause,
}

pub fn run() {
    enable_raw_mode().expect("无法启用原始模式");

    let (input_worker_tx, input_worker_rx): (mpsc::Sender<CliEvent>, mpsc::Receiver<CliEvent>) =
        mpsc::channel();

    // 创建共享定时器状态
    let timer_state = Arc::new(Mutex::new(TimerState::Running));
    let input_state = timer_state.clone();

    poll_input_thread(input_worker_tx, input_state);
    main_loop(input_worker_rx, timer_state);
}

fn main_loop(
    input_worker_rx: mpsc::Receiver<CliEvent>,
    timer_state: Arc<Mutex<TimerState>>,
) {
    let mut stdout = std::io::stdout();
    let backend = crossterm::CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend).expect("无法创建终端");
    terminal.clear().expect("无法清除终端");

    let mut remaining_time = 20;
    let mut last_update = Instant::now();
    const UPDATE_INTERVAL: Duration = Duration::from_secs(1);
    const POLL_INTERVAL: Duration = Duration::from_millis(50); // 短间隔轮询输入

    loop {
        // 处理输入事件
        match handle_input(&input_worker_rx) {
            InputEvent::Quit => {
                *timer_state.lock().unwrap() = TimerState::Exited;
                break;
            }
            InputEvent::TogglePause => {
                let mut state = timer_state.lock().unwrap();
                *state = match *state {
                    TimerState::Running => TimerState::Paused,
                    TimerState::Paused => TimerState::Running,
                    TimerState::Exited => TimerState::Exited,
                };
                // 切换状态时重置计时起点,避免暂停期间累积时间
                last_update = Instant::now();
            }
            InputEvent::Continue => {}
        }

        let state = timer_state.lock().unwrap();
        if *state == TimerState::Exited {
            break;
        }

        // 仅在运行状态下更新时间
        if *state == TimerState::Running && last_update.elapsed() >= UPDATE_INTERVAL {
            remaining_time -= 1;
            let time = seconds_to_time(remaining_time);
            render(&mut terminal, &time);
            last_update = Instant::now();

            if remaining_time <= 0 {
                *timer_state.lock().unwrap() = TimerState::Exited;
                break;
            }
        }

        // 短时间睡眠,避免CPU占用过高
        thread::sleep(POLL_INTERVAL);
    }

    quit(&mut terminal, Some("再见!"));
}

pub fn poll_input_thread(
    input_worker_tx: mpsc::Sender<CliEvent>,
    timer_state: Arc<Mutex<TimerState>>,
) {
    let tick_rate = Duration::from_millis(200);

    thread::spawn(move || {
        let mut last_tick = Instant::now();
        loop {
            // 检查是否需要退出线程
            if *timer_state.lock().unwrap() == TimerState::Exited {
                break;
            }

            let timeout = tick_rate
                .checked_sub(last_tick.elapsed())
                .unwrap_or_else(|| Duration::from_secs(0));

            if event::poll(timeout).expect("事件轮询失败") {
                let crossterm_event = event::read().expect("读取事件失败");
                input_worker_tx
                    .send(CliEvent::Input(crossterm_event))
                    .expect("发送事件失败");
            }

            if last_tick.elapsed() >= tick_rate && input_worker_tx.send(CliEvent::Tick).is_ok() {
                last_tick = Instant::now();
            }
        }
    });
}

pub fn handle_input(input_worker_rx: &mpsc::Receiver<CliEvent>) -> InputEvent {
    match input_worker_rx.try_recv() {
        Ok(CliEvent::Input(Event::Key(key_event))) => match key_event {
            KeyEvent {
                code: KeyCode::Char('q'),
                ..
            }
            | KeyEvent {
                code: KeyCode::Char('c'),
                modifiers: KeyModifiers::CONTROL,
                ..
            } => InputEvent::Quit,
            KeyEvent {
                code: KeyCode::Char('p'),
                ..
            } => InputEvent::TogglePause, // 按p键切换暂停/继续
            _ => InputEvent::Continue,
        },
        Ok(CliEvent::Tick) => InputEvent::Continue,
        Err(mpsc::TryRecvError::Empty) => InputEvent::Continue,
        Err(mpsc::TryRecvError::Disconnected) => {
            eprintln!("输入处理线程断开连接");
            InputEvent::Quit
        }
    }
}

// 辅助函数(模拟原代码中的util::seconds_to_time)
fn seconds_to_time(seconds: u32) -> String {
    format!("{}:{:02}", seconds / 60, seconds % 60)
}

// 辅助函数(模拟原代码中的view::render)
fn render(terminal: &mut Terminal<crossterm::CrosstermBackend<std::io::Stdout>>, time: &str) {
    terminal.clear().unwrap();
    writeln!(terminal, "番茄钟: {}", time).unwrap();
    terminal.flush().unwrap();
}

// 辅助函数(模拟原代码中的quit)
fn quit(
    terminal: &mut Terminal<crossterm::CrosstermBackend<std::io::Stdout>>,
    message: Option<&str>,
) {
    disable_raw_mode().expect("无法禁用原始模式");
    terminal.clear().unwrap();
    if let Some(msg) = message {
        writeln!(terminal, "{}", msg).unwrap();
    }
    terminal.flush().unwrap();
}

关键改进点说明

  • 非阻塞计时:使用Instant跟踪上次时间更新,配合50ms的短间隔睡眠,既保证输入响应及时,又准确控制定时器每秒递减。
  • 状态控制:通过Arc<Mutex<TimerState>>实现线程间的状态共享,输入线程可触发暂停/继续/退出操作,主线程根据状态调整行为。
  • 暂停逻辑:暂停时停止时间递减,恢复时重置计时起点,避免暂停期间的时间累积。
  • 线程退出:输入线程定期检查退出状态,确保程序退出时所有线程能正确终止。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 08:10:31