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

Rust CLI工具ESC键延迟检测与程序无法立即退出排查

问题原因与修复方案

1. ESC键检测延迟的原因

你的代码中,stdout_reader.next()是同步阻塞操作,却被放在了Tokio的select!异步循环里。Tokio的异步运行时依赖非阻塞任务调度,同步阻塞操作会占用Tokio工作线程,导致rx.recv()分支无法被及时调度——即使键盘监听线程已经发送了ESC信号,主循环也会卡在next()调用上,直到该同步操作返回,才会响应退出信号。

2. 检测ESC后程序不立即退出的原因

  • 终端在raw模式下,println!("finished!")输出的换行符不会自动触发终端回车换行,光标停留在错误位置,给你程序未退出的错觉;
  • 未彻底重置终端状态,raw模式下的输入缓冲可能残留未处理字符,需要手动触发终端刷新;
  • SSH命令的stdout流未被主动关闭,stdout_reader还在等待数据,延迟程序退出。

修复后的代码示例

use crossterm::{
    event::{self, Event, KeyCode, KeyEvent},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType, MoveToColumn, Reset},
    Result,
};
use tokio::sync::mpsc;
use tokio_util::codec::{FramedRead, LinesCodec};

// 假设command为异步SSH命令,stdout返回AsyncRead类型
let mut stdout_reader = FramedRead::new(
    command.stdout().take().expect("Failed to open stdout"),
    LinesCodec::new(),
);

println!("Starting the thread");
enable_raw_mode()?;
let (tx, mut rx) = mpsc::channel(32);

// 键盘监听线程:仅关注ESC键
std::thread::spawn(move || loop {
    match event::read() {
        Ok(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => {
            let _ = tx.blocking_send(());
            break;
        }
        Ok(_) => continue,
        Err(_) => break,
    }
});

loop {
    tokio::select! {
        // 处理退出信号
        _ = rx.recv() => {
            execute!(
                std::io::stdout(),
                Clear(ClearType::CurrentLine),
                Print("Detected end\n"),
                MoveToColumn(0),
            )?;
            // 主动关闭SSH stdout流,加速退出
            if let Some(mut stdout) = command.stdout().take() {
                let _ = stdout.shutdown().await;
            }
            break;
        },
        // 异步读取SSH输出,避免阻塞Tokio线程
        Some(Ok(next_line)) = stdout_reader.next() => {
            execute!(
                std::io::stdout(),
                Clear(ClearType::CurrentLine),
                Print(next_line),
                Print("\n"),
                MoveToColumn(0),
            )?;
        }
    }
}

// 彻底重置终端状态,恢复默认行为
execute!(std::io::stdout(), Reset)?;
disable_raw_mode()?;
// 手动刷新输出,确保内容立即显示
std::io::stdout().flush()?;
println!("finished!");

关键修复点

  • 使用异步FramedRead:替换为tokio_util::codec::FramedRead,让next()成为异步操作,避免阻塞Tokio工作线程;
  • 简化键盘监听逻辑:只响应ESC键,减少不必要的终端输出操作;
  • 主动关闭SSH流:收到退出信号后关闭stdout流,让读取任务快速结束;
  • 重置终端状态:退出前执行Reset命令,彻底恢复终端默认行为;
  • 手动刷新输出:确保finished!立即显示,避免缓冲导致的视觉延迟。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 10:43:34