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

按下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()永久阻塞。具体修改如下:

关键修改点

  1. 共享子进程句柄到监听线程:将Child实例用Arc<Mutex<Child>>包裹,让监听线程能访问并终止子进程。
  2. 替换阻塞等待为循环检查:主线程不再直接调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 10:37:41