Rust异步Future await时触发panic终止程序,如何避免该问题?
问题原因说明
你之前的方案不生效有两个核心原因:
- 你定义的
call_data返回值是()(空类型),并非Result类型,所以匹配Ok/Err的逻辑本身就不成立,也不可能捕获到错误。 unwrap()方法在遇到空值/错误时会直接触发panic,而非返回Err,所以哪怕你把call_data改成返回Result,只要内部用了unwrap,错误会直接在函数内部触发panic,不会走到外层的匹配逻辑。
解决方案
方案1:从根源避免panic,用错误返回代替unwrap(最推荐)
调整call_data的返回值为Result类型,把unwrap替换为错误返回逻辑,从根源上消灭panic触发点:
use std::error::Error; use tokio::{task, time::{interval, Duration}}; #[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { let forever = task::spawn(async { let mut interval = interval(Duration::from_secs(60)); println!("Start"); loop { interval.tick().await; // 匹配返回的Result if let Err(e) = call_data("BTC-USD", "test3").await { eprintln!("调用call_data出错: {}", e); continue; } // 正常执行后续逻辑 } }); forever.await?; Ok(()) } // 修改返回值为Result,错误类型可根据实际场景替换为自定义错误 async fn call_data(name:&str, table: &str) -> Result<(), Box<dyn Error>> { // 把unwrap换成?,出错时直接返回Err let data = data()?; // 原有数据处理逻辑 Ok(()) } // 假设原data函数返回Result,此处为模拟定义 fn data() -> Result<(), Box<dyn Error>> { // 原有业务逻辑 Ok(()) }
方案2:捕获现有panic(适合无法修改call_data内部实现的场景)
如果没办法修改call_data内部的unwrap逻辑,可以用std::panic::catch_unwind捕获异步调用过程中的panic,避免整个程序退出:
use std::panic::{catch_unwind, AssertUnwindSafe}; use tokio::{task, time::{interval, Duration}}; #[tokio::main] async fn main() { let forever = task::spawn(async { let mut interval = interval(Duration::from_secs(60)); println!("Start"); loop { interval.tick().await; // 用catch_unwind捕获panic let res = catch_unwind(AssertUnwindSafe(async { call_data("BTC-USD", "test3").await; })); if res.is_err() { eprintln!("call_data触发panic,跳过本次循环"); continue; } // 正常执行后续逻辑 } }); let _ = forever.await; } // 未修改内部实现的原有call_data async fn call_data(name:&str, table: &str){ data().unwrap(); }
方案3:通过独立子task隔离panic
Tokio中单独spawn的子task如果panic,不会直接终止整个程序,只会让对应的JoinHandle返回错误,你也可以每次调用都单独spawn一个子task来隔离风险:
use tokio::{task, time::{interval, Duration}}; #[tokio::main] async fn main() { let forever = task::spawn(async { let mut interval = interval(Duration::from_secs(60)); println!("Start"); loop { interval.tick().await; // 单独spawn子task执行call_data let task_res = task::spawn(async { call_data("BTC-USD", "test3").await; }).await; if task_res.is_err() { eprintln!("call_data执行出错,跳过本次循环"); continue; } // 正常执行后续逻辑 } }); let _ = forever.await; } // 未修改内部实现的原有call_data async fn call_data(name:&str, table: &str){ data().unwrap(); }
内容的提问来源于stack exchange,提问作者Lee Alex
相关产品推荐
相关产品推荐

