按下Ctrl+C时如何优雅停止命令并退出进程?
问题:按下Ctrl+C无法立即终止进程,需等待命令执行完毕退出
当前按下Ctrl+C时,代码会退出监听Ctrl+C的loop,但进程仅会在命令执行完毕后才退出。问题出在child.wait().expect("failed to wait for command")这一行,但无法移除它——未按下Ctrl+C时必须等待命令执行完成。请问如何实现按下Ctrl+C时优雅停止命令并退出进程?
相关代码如下:
use std::{ io::Write, process::{Command, Stdio}, sync::{Arc, Mutex}, thread, time::Duration, }; use termion::{event::Key, input::TermRead, raw::IntoRawMode}; pub struct CmdRunner {} impl CmdRunner { pub fn run<W: Write + Send + 'static>(&mut self, stdout_mutex: Arc<Mutex<Option<W>>>) { let mut command = Command::new("script"); command.arg("-qec").arg("sleep 6").arg("/dev/null"); command.stdout(Stdio::piped()); command.stderr(Stdio::piped()); let mut child = command.spawn().expect("failed to spawn command"); let should_exit = Arc::new(Mutex::new(false)); let should_exit_clone = Arc::clone(&should_exit); let mut stdin = termion::async_stdin().keys(); let handle = std::thread::spawn(move || { loop { if *should_exit_clone.lock().unwrap() { println!("Exited!"); break; } let input = stdin.next(); if let Some(Ok(key)) = input { match key { Key::Ctrl('c') => { *should_exit_clone.lock().unwrap() = true; } _ => {} } } thread::sleep(Duration::from_millis(50)); } }); child.wait().expect("failed to wait for command"); println!("The command finished executing!"); *should_exit.lock().unwrap() = true; handle.join().unwrap(); } } fn main() { let stdout = std::io::stdout().into_raw_mode().unwrap(); let mut command = CmdRunner {}; let stdout_mutex = Arc::new(Mutex::new(Some(stdout))); command.run(stdout_mutex); }
解决方案
核心是在捕获Ctrl+C时主动终止子进程,同时让主线程避免被child.wait()永久阻塞。具体修改如下:
关键修改点
- 共享子进程句柄到监听线程:将
Child实例用Arc<Mutex<Child>>包裹,让监听线程能访问并终止子进程。 - 替换阻塞等待为循环检查:主线程不再直接调用
child.wait(),而是通过循环定期检查子进程状态,同时监听退出标记,确保能及时响应Ctrl+C信号。
修改后的完整代码
use std::{ io::Write, process::{Command, Child, Stdio}, sync::{Arc, Mutex}, thread, time::Duration, }; use termion::{event::Key, input::TermRead, raw::IntoRawMode}; pub struct CmdRunner {} impl CmdRunner { pub fn run<W: Write + Send + 'static>(&mut self, stdout_mutex: Arc<Mutex<Option<W>>>) { let mut command = Command::new("script"); command.arg("-qec").arg("sleep 6").arg("/dev/null"); command.stdout(Stdio::piped()); command.stderr(Stdio::piped()); let child = command.spawn().expect("failed to spawn command"); let child = Arc::new(Mutex::new(child)); let child_clone = Arc::clone(&child); let should_exit = Arc::new(Mutex::new(false)); let should_exit_clone = Arc::clone(&should_exit); let mut stdin = termion::async_stdin().keys(); // 监听输入的线程 let handle = std::thread::spawn(move || { loop { if *should_exit_clone.lock().unwrap() { println!("Exited!"); break; } if let Some(Ok(Key::Ctrl('c'))) = stdin.next() { // 捕获Ctrl+C,终止子进程并设置退出标记 if let Ok(mut child) = child_clone.lock() { let _ = child.kill(); } *should_exit_clone.lock().unwrap() = true; } thread::sleep(Duration::from_millis(50)); } }); // 主线程循环检查子进程状态和退出标记 loop { // 检查子进程是否已结束 let mut child_guard = child.lock().unwrap(); if let Ok(exit_status) = child_guard.try_wait() { if exit_status.is_some() { println!("The command finished executing!"); break; } } drop(child_guard); // 提前释放锁,避免阻塞监听线程 // 检查是否需要退出 if *should_exit.lock().unwrap() { break; } thread::sleep(Duration::from_millis(50)); } // 通知监听线程退出并等待其结束 *should_exit.lock().unwrap() = true; handle.join().unwrap(); } } fn main() { let stdout = std::io::stdout().into_raw_mode().unwrap(); let mut command = CmdRunner {}; let stdout_mutex = Arc::new(Mutex::new(Some(stdout))); command.run(stdout_mutex); }
代码说明
- 监听线程捕获到Ctrl+C时,会立即调用
child.kill()终止子进程,同时设置should_exit标记。 - 主线程通过
try_wait()非阻塞检查子进程状态,既保证了正常情况下等待命令完成,又能在收到退出信号时及时跳出循环。 - 手动释放
child_guard锁,避免主线程持有锁时阻塞监听线程操作子进程。
内容的提问来源于stack exchange,提问作者wyc
相关产品推荐
相关产品推荐

