跳到主要内容

异步代码调试

问题

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: 如何检测异步代码中的阻塞?

答案

  1. 编译时:启用 clippy::await_holding_lock lint
  2. 运行时tokio-console 可以显示长时间不 yield 的 task
  3. 测试tokio::test + tokio::time::pause() 模拟时间
  4. 经验:所有 std::fsstd::thread::sleepMutex::lock 在 async 中都应替换为 tokio 版本

相关链接