【发布时间】:2023-02-16 10:31:39
【问题描述】:
我正在使用 Tokio 编写命令行应用程序,它通过侦听键盘中断事件(即 ctrl + c)来控制其生命周期;但是,与此同时,它还必须监视生成的其他任务,并可能在任何任务崩溃或遇到错误时启动提前关闭。为此,我将 tokio::select 包装在 while 循环中,一旦应用程序至少有机会安全关闭,该循环就会终止。
但是,一旦 select 块轮询 tokio::signal::ctrl_c 返回的未来,主线程就会出现恐慌并显示以下消息:
thread 'main' panicked at 'there is no signal driver running, must be called from the context of Tokio runtime'
...这令人困惑,因为这一切都是在 Runtime::block_on 调用中完成的。我还没有发布此应用程序(但),但可以使用以下代码重现该问题:
use tokio::runtime::Builder;
use tokio::signal;
use tokio::sync::watch;
use tokio::task::JoinSet;
fn main() {
let runtime = Builder::new_multi_thread().worker_threads(2).build().unwrap();
runtime.block_on(async {
let _rt_guard = runtime.enter();
let (ping_tx, mut ping_rx) = watch::channel(0u32);
let (pong_tx, mut pong_rx) = watch::channel(0u32);
let mut tasks = JoinSet::new();
let ping = tasks.spawn(async move {
let mut val = 0u32;
ping_tx.send(val).unwrap();
while val < 10u32 {
pong_rx.changed().await.unwrap();
val = *pong_rx.borrow();
ping_tx.send(val + 1).unwrap();
println!("ping! {}", val + 1);
}
});
let pong = tasks.spawn(async move {
let mut val = 0u32;
while val < 10u32 {
ping_rx.changed().await.unwrap();
val = *ping_rx.borrow();
pong_tx.send(val + 1).unwrap();
println!("pong! {}", val + 1);
}
});
let mut interrupt = Box::pin(signal::ctrl_c());
let mut interrupt_read = false;
while !interrupt_read && !tasks.is_empty() {
tokio::select! {
biased;
_ = &mut interrupt, if !interrupt_read => {
ping.abort();
pong.abort();
interrupt_read = true;
},
_ = tasks.join_next() => {}
}
}
});
}
这个例子有点做作,但重要的部分是:
- 我有意使用
Runtime::block_on()而不是tokio::main,因为我想在运行时控制运行时线程的数量。- 不过,奇怪的是,如果重写为使用
tokio::main,此示例仍然有效。
- 不过,奇怪的是,如果重写为使用
- 我添加了
let _rt_guard = runtime.enter()以确保设置了运行时上下文,但它的存在与否似乎没有什么不同。
【问题讨论】:
标签: rust signals rust-tokio