异步代码调试
问题
Rust 异步代码有哪些常见问题?如何调试?
答案
1. 阻塞异步运行时
// ❌ 错误:在 async 中执行阻塞操作
async fn bad() {
std::thread::sleep(Duration::from_secs(5)); // 阻塞整个 Tokio worker 线程!
std::fs::read_to_string("file.txt").unwrap(); // 同步 IO,阻塞!
}
// ✅ 正确:使用异步版本或 spawn_blocking
async fn good() {
tokio::time::sleep(Duration::from_secs(5)).await;
tokio::fs::read_to_string("file.txt").await.unwrap();
// CPU 密集工作用 spawn_blocking
let result = tokio::task::spawn_blocking(|| {
heavy_computation()
}).await.unwrap();
}
2. Future 未被 .await
// ❌ 警告:Future 未被执行
async fn fetch() -> String { "data".into() }
async fn handler() {
fetch(); // 只创建了 Future,没有执行!
// warning: unused implementer of `Future` that must be used
}
// ✅ 正确
async fn handler() {
let data = fetch().await; // 必须 .await
}
3. 跨 .await 持有锁
// ❌ 潜在死锁
use std::sync::Mutex;
let mutex = Mutex::new(0);
async fn bad(mutex: &Mutex<i32>) {
let guard = mutex.lock().unwrap();
tokio::time::sleep(Duration::from_secs(1)).await;
// guard 跨越了 .await → 如果另一个 task 获取锁,可能死锁
drop(guard);
}
// ✅ 修复方案
async fn good(mutex: &Mutex<i32>) {
{
let mut guard = mutex.lock().unwrap();
*guard += 1;
} // guard 在 .await 之前释放
tokio::time::sleep(Duration::from_secs(1)).await;
}
// 或使用 tokio::sync::Mutex(异步友好但有性能开销)
调试工具
| 工具 | 用途 |
|---|---|
tokio-console | 实时查看 task 状态、waker 调用 |
tracing | 结构化异步日志 |
#[instrument] | 自动追踪 async fn 的进入/退出 |
use tracing::instrument;
#[instrument(skip(db))]
async fn get_user(db: &Database, id: u64) -> User {
// 自动记录:函数进入、参数值、耗时
db.find_user(id).await
}
常见面试问题
Q1: 如何检测异步代码中的阻塞?
答案:
- 编译时:启用
clippy::await_holding_locklint - 运行时:
tokio-console可以显示长时间不 yield 的 task - 测试:
tokio::test+tokio::time::pause()模拟时间 - 经验:所有
std::fs、std::thread::sleep、Mutex::lock在 async 中都应替换为 tokio 版本