为何使用Tokio spawn时,我的Fn需要满足Sync约束?
为何
MyFn必须添加Sync约束? 以下代码触发编译器错误,提示MyFn应添加Sync约束:
use std::future::Future; fn spawn<MyFn, Out>(func: MyFn) where Out: Future + Send + 'static, MyFn: Fn() -> Out + Send + 'static, { tokio::spawn(call_func(func)); } async fn call_func<MyFn, Out>(func: MyFn) where MyFn: Fn() -> Out, Out: Future, { func().await; func().await; }
编译时出现如下错误:
Compiling playground v0.0.1 (/playground) error: future cannot be sent between threads safely --> src/main.rs:8:18 | 8 | tokio::spawn(call_func(func)); | ^^^^^^^^^^^^^^^ future returned by `call_func` is not `Send` | note: future is not `Send` as this value is used across an await --> src/main.rs:16:11 | 16 | func().await; | ---- ^^^^^^ await occurs here, with `func` maybe used later | | | has type `&MyFn` which is not `Send` note: `func` is later dropped here --> src/main.rs:16:17 | 16 | func().await; | ^ help: consider moving this into a `let` binding to create a shorter lived borrow --> src/main.rs:16:5 | 16 | func().await; | ^^^^^^ note: required by a bound in `tokio::spawn` --> /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.21.2/src/task/spawn.rs:127:21 | 127 | T: Future + Send + 'static, | ^^^^ required by this bound in `tokio::spawn` help: consider dereferencing here | 8 | tokio::spawn(*call_func(func)); | + help: consider further restricting this bound | 6 | MyFn: Fn() -> Out + Send + 'static + std::marker::Sync, | +++++++++++++++++++ error: could not compile `playground` due to previous error
根据tokio::spawn的定义,它仅要求:
pub fn spawn<T>(future: T) -> JoinHandle<T::Output>ⓘ where T: Future + Send + 'static, T::Output: Send + 'static,
原因分析
核心逻辑在于call_func返回的Future的Send属性依赖MyFn的Sync标记:
- 在
call_func中,func被两次调用并await,编译器生成的Future会在第一个await点保留func的引用——因为后续还要再次调用它,这个引用会跨await点存在。 tokio::spawn要求提交的Future必须是Send的,这意味着Future中所有跨await点持有的数据都得是Send的。- 这里跨await点持有的是
&MyFn,而引用类型&T要具备Send性,前提是T实现Sync(Sync表示T可以被多个线程安全地共享引用)。 - 因此,要让
call_func的返回Future满足Send,必须给MyFn添加Sync约束,确保&MyFn是Send的,最终让整个Future符合tokio::spawn的要求。
另外还有一种替代方案:通过let绑定提前获取Future,缩短func的引用生命周期,这样就不需要Sync约束:
async fn call_func<MyFn, Out>(func: MyFn) where MyFn: Fn() -> Out, Out: Future, { let fut1 = func(); fut1.await; let fut2 = func(); fut2.await; }
这种写法里,func的引用不会跨await点存在,编译器也就不会要求MyFn实现Sync。
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

