信号处理

像命令行应用程序这样的进程,需要对操作系统发送的信号作出反应。最常见的例子可能是Ctrl +C,通常指示进程终止的信号。要在 Rust 程序中处理信号,您需要考虑如何接收这些信号,以及如何对它们作出反应。

操作系统之间的差异

在 UNIX 系统(如 Linux、MacOS 和 FreeBSD)上,一个进程可以接收信号。可以以默认(操作系统提供的)方式对信号作出反应,捕获信号并以程序定义的方式处理它们,或者完全忽略信号。

Windows 没有信号。你可以用控制台处理程序定义在事件发生时,执行的回调。还有结构化异常处理,它处理所有类型的系统异常,如除数为零、无效访问异常、堆栈溢出等。

第一步:处理 ctrl+c

这个ctrlc箱子所做的,正是它的名字所暗示的:它允许你对用户按下Ctrl +C,以跨平台的方式。使用箱子的主要方法是:

fn main() {
    ctrlc::set_handler(move || {
        println!("received Ctrl+C!");
    }).expect("Error setting Ctrl-C handler");

    // ...
}

当然,这并没有那么有帮助:它只打印一条消息,并且不会停止程序。

在一个真实的程序中,最好在信号处理程序中,设置一个变量,然后在程序中的各个地方进行检查。例如,可以在信号处理程序中设置Arc<AtomicBool>(线程之间可共享的布尔值),在热循环中,或者在等待线程时,您会定期检查其值,并在该值变为真时,中断。

处理其他类型的信号

这个ctrlc箱子仅处理Ctrl +C或者,在 UNIX 系统上称为SIGINT(中断信号)。要对更多的 Unix 信号,作出反应,您应该看看信号钩子。 其设计在这篇博客文章上有所描述,并且这是目前社区支持最广泛的库了。

下面是一个简单的例子:

use std::{error::Error, thread};
use signal_hook::{iterator::Signals, SIGINT};

fn main() -> Result<(), Box<Error>> {
    let signals = Signals::new(&[SIGINT])?;

    thread::spawn(move || {
        for sig in signals.forever() {
            println!("Received signal {:?}", sig);
        }
    });

    Ok(())
}

使用通道

您可以使用通道,而不是设置一个变量并让程序的其他部分检查它:您创建一个通道,每当接收到信号时,信号处理程序就向该通道发送一个值。在应用程序代码中,您可以使用此通道与其他通道的联系,作为线程之间的同步桥梁。使用crossbeam-channel箱子,它看起来像这样:

use std::time::Duration;
use crossbeam_channel::{bounded, tick, Receiver, select};

fn ctrl_channel() -> Result<Receiver<()>, ctrlc::Error> {
    let (sender, receiver) = bounded(100);
    ctrlc::set_handler(move || {
        let _ = sender.send(());
    })?;

    Ok(receiver)
}

fn main() -> Result<(), exitfailure::ExitFailure> {
    let ctrl_c_events = ctrl_channel()?;
    let ticks = tick(Duration::from_secs(1));

    loop {
        select! {
            recv(ticks) -> _ => {
                println!("working!");
            }
            recv(ctrl_c_events) -> _ => {
                println!();
                println!("Goodbye!");
                break;
            }
        }
    }

    Ok(())
}

使用 future 和 stream

如果您正在使用tokio,您很可能已经用异步模式和事件驱动设计,编写了应用程序。您可以启用信号钩子的tokio-support功能,而不是直接使用 crossbeam 的 channels。这可以让你在信号钩子的Signals类型上,调用.into_async(),以获取实现了futures::Stream的新类型。

当您在处理第一个 ctrl+c 时,收到另一个 ctrl+c 时要做什么?

大多数用户会按Ctrl +C,然后给你的程序几秒钟退出,或者告诉他们发生了什么。如果那不发生,他们再一次按Ctrl +C。典型的处理行为是让应用程序立即退出。