症状
这个错误就像带你导览自己的 future:error: future cannot be sent between threads safely,note 指出“future 不是 Send,因为这个值跨 await 使用”,并精确给出捕获变量的类型与那个 await 的位置。两种不同的病共享这个症状:类型错了——Rc 被捕获而运行时可能迁移任务,换成 Arc;类型对但作用域错了——std::sync::MutexGuard 跨 await 存活,把本来没问题的 future 变成 !Send,守卫必须在 await 之前 drop。同一逻辑在 current-thread 运行时或直接调用时正常,迁到 #[tokio::main] 多线程运行时才暴露,是典型的时间线。
诊断思路
先读 note 拿到捕获类型与 await 位置——修复范围就此确定;再区分两种变体(类型问题换 Arc,守卫问题收窄作用域);最后把测试改成 #[tokio::test(flavor = “multi_thread”)] 并 –test-threads=4 跑,堵住“current-thread 测试全过、生产 spawn 失败”的缺口。
根因
tokio::spawn 要求 future 是 Send,因为多线程调度器可能在另一个 worker 线程恢复它。async 块若跨 await 持有非 Send 值(Rc、std 守卫、线程局部状态),生成的状态机整体就是 !Send,spawn 在编译期被拒。
修复与验证
按病灶下药:跨 await 的共享状态换 Arc 配 tokio::sync::Mutex;std 守卫用块作用域在 await 前 drop(先在块里取快照再 await);Rc 密集的同步代码局部化到边界一侧——同步函数内部随便用 Rc,返回拥有类型再进 async 块;确实做不成 Send 的胶水代码就显式选 current-thread 运行时。验证时 spawn 的任务在多线程运行时编译并运行,四线程并发测试保持全绿。别把所有 std::sync::Mutex 无脑换成 tokio 版——从不当 await 的路径白白承担异步开销,能收窄作用域就收窄。
代码示例
error: future cannot be sent between threads safely
|
note: future is not `Send` as this value is used across an await
--> src/service.rs:58:9
|
54 | let counter = Rc::new(RefCell::new(0));
| ------- has type `Rc<RefCell<i32>>` which is not `Send`
...
58 | do_thing().await; // counter alive across this await
let counter = Arc::new(tokio::sync::Mutex::new(0));
// or when no await happens inside: scope the std guard
// guard scoped, dropped before await:
let snapshot = {
let g = std_counter.lock().unwrap();
g.clone()
}; // g drops here
do_thing(&snapshot).await;
let prepared = prepare_with_rc(data); // sync, Rc inside, returns owned
spawn(async move {
use_owned(prepared).await; // no Rc in the state machine
});
tokio::runtime::Builder::new_current_thread()
.enable_all().build()
.block_on(single_threaded_main());