信號處理
包含命令列應用程式的過程需要對作業系統發送的信號產生反應。最常見的範例可能是 Ctrl+C,這個信號通常告訴一個程序要中止執行。若要處理 Rust 程式中的信號,你需要思考如何接收到這些信號以及如何對它們做出反應。
不同作業系統之間的差異
在 Unix 系統(像 Linux、macOS 和 FreeBSD)中,一個程序可以接收到 信號。它可以以預設的方式(由作業系統提供)對它們產生反應,捕捉信號並以程式定義的方式處理它們,或是完全忽略信號。
Windows 沒有信號。你可以使用 主控台處理常式 來定義在事件發生時執行哪些回呼。另外也有 結構化例外狀況處理,負責處理各式各樣的系統例外狀況,例如零除、非法存取例外狀況、堆疊溢位等等。
首先:處理 Ctrl+C
ctrlc crate 的功能與其名稱相符,它可以讓你跨平台對使用者按下 Ctrl+C 作出反應。crate 的主要用法如下:
use std::{thread, time::Duration};
fn main() {
ctrlc::set_handler(move || {
println!("received Ctrl+C!");
})
.expect("Error setting Ctrl-C handler");
// Following code does the actual work, and can be interrupted by pressing
// Ctrl-C. As an example: Let's wait a few seconds.
thread::sleep(Duration::from_secs(2));
}
當然地,這並不算好用:它只會印出訊息,但並不會停止程式執行。
在一個真實世界的程式中,一個不錯的做法是,於訊號處理器中設定一個變數,然後在你的程式中各種地方檢查這個變數。例如,你可以在你的訊號處理器中設定一個 Arc(一個可以跨執行緒分享的布林值),在熱迴圈中,或是在等待執行緒時,定期檢查它的值,當它變為真時中斷。
處理其他類型的訊號
ctrlc 函式庫只處理 Ctrl+C,或是在 Unix 系統中會被稱為 SIGINT
(「中斷」訊號)。若要對更多的 Unix 訊號做出反應,你應該去看一下 signal-hook。它的設計在 這篇文章 中有說明,而且它目前是擁有最廣泛社群支援的函式庫。
以下是一個簡單的範例
use signal_hook::{consts::SIGINT, iterator::Signals};
use std::{error::Error, thread, time::Duration};
fn main() -> Result<(), Box<dyn Error>> {
let mut signals = Signals::new(&[SIGINT])?;
thread::spawn(move || {
for sig in signals.forever() {
println!("Received signal {:?}", sig);
}
});
// Following code does the actual work, and can be interrupted by pressing
// Ctrl-C. As an example: Let's wait a few seconds.
thread::sleep(Duration::from_secs(2));
Ok(())
}
使用通道
與其設定一個變數,讓程式的其他部分檢查,你可以使用通道:你建立一個通道,讓訊號處理器每當收到訊號的時候,都會發射一個值進入這個通道。在你的應用程式碼中,你使用這個和其他通道作為執行緒之間的同步點。使用 crossbeam-channel,它看起來會像這樣
use std::time::Duration;
use crossbeam_channel::{bounded, tick, Receiver, select};
use anyhow::Result;
fn ctrl_channel() -> Result<Receiver<()>, ctrlc::Error> {
let (sender, receiver) = bounded(100);
ctrlc::set_handler(move || {
let _ = sender.send(());
})?;
Ok(receiver)
}
fn main() -> Result<()> {
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 和串流
如果你正在使用 tokio,你很可能已經使用非同步模式和事件驅動設計在撰寫你的應用程式。與其直接使用 crossbeam 的通道,你可以在 signal-hook 中啟用 tokio-support
功能。這允許你對 signal-hook 的 Signals
類型呼叫 .into_async()
來取得一個新的類型,這個類型實作了 futures::Stream
。
當你在處理第一個 Ctrl+C 時收到另一個 Ctrl+C,該怎麼辦
大多數使用者會按下 Ctrl+C,然後給你的程式幾秒鐘的時間讓它離開,或告訴他們發生了什麼事。如果沒有發生,他們會再次按下 Ctrl+C。典型的行為是讓應用程式立刻離開。