如何实现定时器线程的退出、暂停与恢复?——可暂停番茄钟开发疑问
解决方案:非阻塞定时器与线程控制
核心问题分析
当前实现的主要问题是主线程依赖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
相关产品推荐
相关产品推荐

