Rust异步信号处理库async-ctrlc的使用:优雅处理Ctrl-C中断的跨平台解决方案

Rust异步信号处理库async-ctrlc的使用:优雅处理Ctrl-C中断的跨平台解决方案

async-ctrlcctrlc crate的异步包装器,提供了优雅处理Ctrl+C中断信号的跨平台解决方案。

Future示例

use async_ctrlc::CtrlC;
use async_std::{prelude::FutureExt, task::sleep};
use std::time::Duration;

#[async_std::main]
async fn main() {
    // 创建Ctrl+C处理器
    let ctrlc = CtrlC::new().expect("cannot create Ctrl+C handler?");
    println!("Try to press Ctrl+C");
    
    // 使用race方法在Ctrl+C信号和异步任务之间竞争
    ctrlc.race(async {
        let mut i = 0;
        loop {
            println!("... {}", i);
            sleep(Duration::from_secs(1)).await;
            i += 1;
        }
    }).await;
    
    println!("Quitting");
}

Stream示例

use async_ctrlc::CtrlC;
use async_std::prelude::StreamExt as _;

#[async_std::main]
async fn main() {
    // 创建Ctrl+C处理器
    let ctrlc = CtrlC::new().expect("cannot create Ctrl+C handler?");
    println!("Try to press Ctrl+C 3 times");
    
    // 将Ctrl+C信号转换为流,并只取前3次
    let mut stream = ctrlc.enumerate().take(3);
    
    // 处理每次Ctrl+C信号
    while let Some((count, _)) = stream.next().await {
        println!("{} x Ctrl+C!", count + 1);
    }
    
    println!("Quitting");
}

完整示例代码

下面是一个更完整的示例,展示了如何在实际应用中使用async-ctrlc

use async_ctrlc::CtrlC;
use async_std::{prelude::*, task};
use std::time::Duration;

#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 创建Ctrl+C处理器
    let ctrlc = CtrlC::new()?;
    
    // 模拟长时间运行的任务
    let long_running_task = async {
        let mut counter = 0;
        loop {
            println!("Working on task {}...", counter);
            task::sleep(Duration::from_secs(1)).await;
            counter += 1;
            
            // 模拟任务完成条件
            if counter == 10 {
                println!("Task completed successfully!");
                break;
            }
        }
    };
    
    println!("Application started. Press Ctrl+C to interrupt or wait for task to complete.");
    
    // 使用race方法处理中断
    ctrlc.race(long_running_task).await;
    
    // 清理资源
    println!("Performing cleanup...");
    task::sleep(Duration::from_secs(1)).await;
    println!("Application exited gracefully.");
    
    Ok(())
}

特性

  1. 跨平台支持:在Windows、Linux和macOS上都能工作
  2. 异步集成:与async-std和tokio等异步运行时无缝集成
  3. 灵活的使用模式
    • 作为Future使用,与任务竞争
    • 作为Stream使用,处理多次中断信号
  4. 轻量级:库大小仅约12.1 KiB

安装

在项目目录中运行以下Cargo命令:

cargo add async-ctrlc

或者在Cargo.toml中添加:

async-ctrlc = "1.2.0"

许可证

MIT或Apache-2.0。


1 回复

Rust异步信号处理库async-ctrlc的使用:优雅处理Ctrl-C中断的跨平台解决方案

async-ctrlc是一个Rust库,提供了跨平台的异步Ctrl-C信号处理功能,特别适合需要优雅关闭的异步应用程序。

主要特性

  • 跨平台支持(Windows/Linux/macOS)
  • 基于Tokio的异步处理
  • 简单易用的API
  • 支持多次注册处理函数

使用方法

基本用法

use async_ctrlc::CtrlC;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    // 创建Ctrl-C处理器
    let ctrlc = CtrlC::new().expect("无法创建Ctrl-C处理器");
    
    // 注册处理函数
    let task = tokio::spawn(async move {
        ctrlc.await;
        println!("收到Ctrl-C信号,正在优雅关闭...");
    });

    println!("程序运行中,按Ctrl-C终止...");
    
    // 模拟长时间运行的任务
    loop {
        println!("处理中...");
        sleep(Duration::from_secs(1)).await;
    }
    
    // 等待Ctrl-C任务完成
    task.await.unwrap();
}

多个处理函数

use async_ctrlc::CtrlC;
use tokio::sync::broadcast;

#[tokio::main]
async fn main() {
    let ctrlc = CtrlC::new().unwrap();
    let (shutdown_sender, _) = broadcast::channel(1);
    
    // 任务1
    let ctrlc1 = ctrlc.clone();
    let sender1 = shutdown_sender.clone();
    tokio::spawn(async move {
        ctrlc1.await;
        println!("处理程序1收到关闭信号");
        sender1.send(()).unwrap();
    });
    
    // 任务2
    let ctrlc2 = ctrlc.clone();
    let sender2 = shutdown_sender.clone();
    tokio::spawn(async move {
        ctrlc2.await;
        println!("处理程序2收到关闭信号");
        sender2.send(()).unwrap();
    });
    
    // 主循环
    let mut receiver = shutdown_sender.subscribe();
    loop {
        tokio::select! {
            _ = receiver.recv() => {
                println!("所有处理程序已完成,正在退出...");
                break;
            }
            _ = tokio::time::sleep(Duration::from_secs(1)) => {
                println!("主程序运行中...");
            }
        }
    }
}

与Tokio信号处理结合

use async_ctrlc::CtrlC;
use tokio::signal::unix::{signal, SignalKind};

#[tokio::main]
async fn main() {
    let ctrlc = CtrlC::new().unwrap();
    let mut sigterm = signal(SignalKind::terminate()).unwrap();
    
    tokio::select! {
        _ = ctrlc => {
            println!("收到Ctrl-C信号");
        }
        _ = sigterm.recv() => {
            println!("收到SIGTERM信号");
        }
    }
    
    println!("正在执行清理工作...");
    tokio::time::sleep(Duration::from_secs(1)).await;
    println!("程序已退出");
}

完整示例代码

下面是一个结合了上述所有功能的完整示例,展示了如何使用async-ctrlc处理Ctrl-C信号,同时结合其他系统信号和多个处理程序:

use async_ctrlc::CtrlC;
use tokio::{
    signal::unix::{signal, SignalKind},
    sync::broadcast,
    time::{sleep, Duration},
};

#[tokio::main]
async fn main() {
    // 创建Ctrl-C处理器
    let ctrlc = CtrlC::new().expect("创建Ctrl-C处理器失败");
    
    // 创建广播通道用于协调关闭
    let (shutdown_sender, _) = broadcast::channel(1);
    
    // 注册Ctrl-C处理程序1
    let ctrlc1 = ctrlc.clone();
    let sender1 = shutdown_sender.clone();
    tokio::spawn(async move {
        ctrlc1.await;
        println!("[处理程序1] 收到Ctrl-C信号");
        sender1.send(()).expect("发送关闭信号失败");
    });
    
    // 注册Ctrl-C处理程序2
    let ctrlc2 = ctrlc.clone();
    let sender2 = shutdown_sender.clone();
    tokio::spawn(async move {
        ctrlc2.await;
        println!("[处理程序2] 收到Ctrl-C信号");
        sender2.send(()).expect("发送关闭信号失败");
    });
    
    // 注册SIGTERM处理程序
    let mut sigterm = signal(SignalKind::terminate()).expect("创建SIGTERM处理器失败");
    let sender3 = shutdown_sender.clone();
    tokio::spawn(async move {
        sigterm.recv().await;
        println!("[处理程序3] 收到SIGTERM信号");
        sender3.send(()).expect("发送关闭信号失败");
    });
    
    println!("程序已启动,按Ctrl-C或发送SIGTERM信号终止...");
    
    // 主循环
    let mut receiver = shutdown_sender.subscribe();
    loop {
        tokio::select! {
            _ = receiver.recv() => {
                println!("收到关闭信号,开始清理...");
                // 模拟清理工作
                sleep(Duration::from_secs(2)).await;
                println!("清理完成,程序退出");
                break;
            }
            _ = sleep(Duration::from_secs(1)) => {
                println!("主程序正常运行中...");
            }
        }
    }
}

安装

在Cargo.toml中添加依赖:

[dependencies]
async-ctrlc = "1.2"
tokio = { version = "1.0", features = ["full"] }

注意事项

  1. 在Windows上,async-ctrlc使用控制台控制处理器,而在Unix系统上使用信号处理
  2. 多次调用CtrlC::new()会创建独立的新实例,每个实例都需要单独等待
  3. 在Tokio运行时中工作最佳,但也支持其他异步运行时

这个库为需要处理用户中断的Rust异步应用程序提供了一种简单而强大的解决方案,特别适合服务器、长时间运行的进程或需要执行清理工作的应用程序。

回到顶部