< 返回版块

yuanzui 发表于 2024-10-22 17:35

Tags:异步,tokio,错误

trait MethodTrait {
    async fn async_method(&self);
}

#[derive(Clone)]
struct MyStruct {}
impl MethodTrait for MyStruct {
    async fn async_method(&self) {
        todo!() // 占位
    }
}

struct Demo<T>
where
    T: MethodTrait + Clone + Sync + Send + 'static,
{
    parser: T,
}

impl<T> Demo<T>
where
    T: MethodTrait + Clone + Sync + Send + 'static,
{
    fn new(parser: T) -> Self {
        Demo { parser }
    }
    async fn execute(&self) {
        let parser = self.parser.clone();
        tokio::spawn(async move {
            // parser.async_method().await; // 这里会报错
            test_fn().await; // 这里正常执行
        });
    }
}

#[tokio::main]
async fn main() {
    let my_struct = MyStruct {};
    let demo = Demo::new(my_struct);
    demo.execute().await;
}

async fn test_fn() {
    println!("hello world")
}

错误信息: the trait Send is not implemented for impl Future<Output = ()>, which is required by : Send

这里没搞懂为什么不能直接写async方法,要写同步方法然后自己手动约束返回值的send。

评论区

写评论
semmyenator 2024-10-25 10:20

--
👇
LazyBoy: tokio默认是启用多线程调度的,所以需要任务满足Send;如果想要单线程的话,需要自己手动指定一个单线程的调度策略

--
👇
缘罪: 我没搞懂这块为啥需要约束Send

在 Rust 中,Send 和 Sync 是兩個重要的標記 trait,它們決定了類型是否可以在多線程之間安全地發送(Send)或者類型本身是否可以安全地在多個線程中共享(Sync)。

當你在 Demo 結構體的方法中調用 tokio::spawn 時,你實際上是在創建一個新的異步任務,並且這個任務需要能夠在任何線程中運行,因此這個任務必須實現 Send。而在你的代碼中,parser.async_method().await; 這一行會觸發一個錯誤,因為 async_method 的返回類型是一個 Future,而默認情況下,閉包和其捕獲的環境(在這個例子中是 parser 的克隆)可能不會自動實現 Send。

為了解決這個問題,你需要確保所有閉包捕獲的數據以及閉包本身都是可發送的(即實現了 Send)。此外,如果你的異步方法 async_method 返回的 Future 也必須是 Send 的。

為了修正這段代碼,你可以確保 parser 實現了 Send 和 Sync,並且在閉包中使用的任何其他數據也都應該實現 Send。對於 async_method,你需要確保它的返回類型 impl Future<Output = ()> 也實現了 Send。

SleepyBoy 2024-10-24 13:43

tokio默认是启用多线程调度的,所以需要任务满足Send;如果想要单线程的话,需要自己手动指定一个单线程的调度策略

--
👇
缘罪: 我没搞懂这块为啥需要约束Send

作者 yuanzui 2024-10-23 09:18

感谢大佬耐心解答,我知道了

--
👇
TinusgragLin: > 我没搞懂这块为啥需要约束Send

哦,我知道了,你的疑惑是不是为啥 tokio::spawn 需要一个 Send 的 Future?

如果我理解没错的话,这是因为 tokio 的任务调度系统默认开了任务窃取,如果某个 worker 线程闲得发慌可以把其他 worker 线程任务队列中的 Future 给这个线程,所以需要 Future 的 ownership 可以在不同线程间转移,所以需要 Send 约束,字节有一个(为负载均衡任务设计的)monoio 异步运行时就没有这个要求了。

TinusgragLin 2024-10-22 18:40

我没搞懂这块为啥需要约束Send

哦,我知道了,你的疑惑是不是为啥 tokio::spawn 需要一个 Send 的 Future?

如果我理解没错的话,这是因为 tokio 的任务调度系统默认开了任务窃取,如果某个 worker 线程闲得发慌可以把其他 worker 线程任务队列中的 Future 给这个线程,所以需要 Future 的 ownership 可以在不同线程间转移,所以需要 Send 约束,字节有一个(为负载均衡任务设计的)monoio 异步运行时就没有这个要求了。

bestgopher 2024-10-22 18:31

学到了 https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=41b5bb02843224f46c7eb5fdedfe44e5

--
👇
TinusgragLin: 如果我记得没错的话,目前 trait 中的 async 方法返回的 Future 默认是没有 Send 约束的,不过现在 nightly 有了 return type notation 之后,就可以加一个这样的约束了:

T::async_method(..): Send
Bai-Jinlin 2024-10-22 18:25

trait MethodTrait {
    async fn async_method(&self);
}

改成

trait MethodTrait {
    fn async_method(&self) -> impl std::future::Future<Output = ()> + Send;
}

参考 https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html#async-fn-in-public-traits

TinusgragLin 2024-10-22 18:22

大概是因为现在 trait 中的 async 方法实际上跟这样写

fn async_method(&self) -> impl Future;

差不多,返回值是一个“opaque type”(实际类型“隐藏”的类型),对于类型系统来说,这就是“某一个”实现了的 Future trait 的类型,而不会考虑它的实际类型,所以类型检查的时候会很保守。

之前官方的博客有一篇相关的文章,你可以参考一下。

作者 yuanzui 2024-10-22 18:19

我没搞懂这块为啥需要约束Send

--
👇
TinusgragLin: 如果我记得没错的话,目前 trait 中的 async 方法返回的 Future 默认是没有 Send 约束的,不过现在 nightly 有了 return type notation 之后,就可以加一个这样的约束了:

T::async_method(..): Send
TinusgragLin 2024-10-22 18:06

如果我记得没错的话,目前 trait 中的 async 方法返回的 Future 默认是没有 Send 约束的,不过现在 nightly 有了 return type notation 之后,就可以加一个这样的约束了:

T::async_method(..): Send
1 共 9 条评论, 1 页