【问题标题】:How do i run a future without awaiting it? (in rust)我如何在不等待的情况下经营未来? (生锈)
【发布时间】:2022-07-21 17:44:00
【问题描述】:

我有一些异步功能

async fn get_player(name: String, i: Instant) -> Option<Player> {
// some code here that returns a player structs
}

在我的主函数中,我想在一个循环中同时运行上述函数,这个函数大约需要 1 秒才能完成,我需要运行它至少 50 次,因此我想让它同时运行这个函数 50 次.在我的主函数中,我有一个lazy_static 自定义Client 结构,不应多次创建。

主要功能

#[tokio::main]
async fn main() {
    client.init().await;

    println!("start");
    for i in 0..10 {
        println!("{}", i);
        let now = Instant::now();

        tokio::spawn(async move  {
            client.get_player("jay".to_string(), now).await;
        });
    }
    loop {}
}

我传递即时的原因是因为在我的 get_player 函数中我有一个 println!() 只打印执行时间。

上面的main方法每个函数调用大约需要500ms,而下面的代码只需要100ms。

#[tokio::main]
async fn maain(){
    client.init().await;

    for i in 0..10 {
        let now = Instant::now();
        client.get_player("jay".to_string(), now).await.expect("panic");
    }
}

但是这个函数仍然是同步代码,我如何真正并发运行异步函数而不需要时间成本?

  • 为了更好地理解 am after 是一个与此类似的实现(它在 java 中顺便说一句),
     CompleteableFuture.thenAccept(x -> x.SayHello(););

或者在 Js 中它类似于 .then 在异步函数之后。

rust 中有没有类似的实现?

【问题讨论】:

  • 每个函数调用的 500 毫秒——这些是同时发生的还是连续发生的?一个函数调用能否在另一个函数的 500 毫秒内启动?
  • 如果你想要并发,我不认为async 是要走的路。 Here is a description of what the differences between these approaches are。在 Rust 中,除非主动轮询,否则期货不会取得进展。 tokio(或其他异步运行时)为您抽象和管理它,所以您能做的最好的事情就是将未来存储在一个变量中,以供以后使用。对于真正的并发,你应该使用线程。
  • @PitaJ 并行意味着并发,但您可以使用上下文切换在单个处理器上运行多个(并发)执行线程,而不是并行运行。
  • 这能回答你的问题吗? tokio join multiple tasks in rust
  • this playground 有帮助吗?是否需要spawn 将取决于您是否需要默认多线程运行时的并行性。

标签: rust rust-tokio rust-async-std


【解决方案1】:

我假设您的 get_player 函数需要一秒钟,因为它等待网络交互,而不是因为某些计算需要那么长时间。如果它是计算密集型的,那么异步是错误的方法,你想改用并行。

此外,我假设get_player 的函数签名实际上是async fn get_player(&amp;self, name: String, i: Instant) -&gt; Option&lt;Player&gt;,否则您的main 代码示例将没有任何意义。虽然我很困惑为什么会是 &amp;self 而不是 &amp;mut self

基于这些假设,我试图重现您的最小可重现示例:

use std::time::{Duration, Instant};

#[derive(Debug)]
struct Player {
    name: String,
}

struct Client {}

impl Client {
    async fn init(&self) {}

    async fn get_player(&self, name: String, _now: Instant) -> Option<Player> {
        // Dummy code that simulates a delay of 1 second
        tokio::time::sleep(Duration::from_millis(1000)).await;
        Some(Player { name })
    }
}

static client: Client = Client {};

#[tokio::main]
async fn main() {
    let begin = Instant::now();
    client.init().await;

    for i in 0..10 {
        let now = Instant::now();
        let player = client
            .get_player(format!("Player #{}", i), now)
            .await
            .expect("panic");
        println!(
            "[{} ms] Retreived player: {:?}",
            begin.elapsed().as_millis(),
            player.name
        );
    }
}
[1002 ms] Retreived player: "Player #0"
[2004 ms] Retreived player: "Player #1"
[3005 ms] Retreived player: "Player #2"
[4008 ms] Retreived player: "Player #3"
[5010 ms] Retreived player: "Player #4"
[6011 ms] Retreived player: "Player #5"
[7013 ms] Retreived player: "Player #6"
[8014 ms] Retreived player: "Player #7"
[9016 ms] Retreived player: "Player #8"
[10018 ms] Retreived player: "Player #9"

这是基于您上一个 main 示例。如您所见,检索所有玩家需要 10 秒,因为它们都是按顺序运行的。

现在让我们异步运行它们。这里的问题是同时加入它们。遗憾的是,Tokio 并没有为此提供简单的方法。你可以tokio::spawn所有人,收集JoinHandles,然后一一加入。但是,板条箱 futures 提供的正是您想要的:

use std::time::{Duration, Instant};

#[derive(Debug)]
struct Player {
    name: String,
}

struct Client {}

impl Client {
    async fn init(&self) {}

    async fn get_player(&self, name: String, _now: Instant) -> Option<Player> {
        // Dummy code her that simulates a delay of 1 second
        tokio::time::sleep(Duration::from_millis(1000)).await;
        Some(Player { name })
    }
}

static client: Client = Client {};

#[tokio::main]
async fn main() {
    let begin = Instant::now();
    client.init().await;

    let get_player_futures = (0..10).into_iter().map(|i| async move {
        let now = Instant::now();
        let player = client
            .get_player(format!("Player #{}", i), now)
            .await
            .expect("panic");
        println!(
            "[{} ms] Retreived player: {:?}",
            begin.elapsed().as_millis(),
            player.name
        );
    });

    futures::future::join_all(get_player_futures).await;
}
[1002 ms] Retreived player: "Player #0"
[1002 ms] Retreived player: "Player #1"
[1002 ms] Retreived player: "Player #2"
[1002 ms] Retreived player: "Player #3"
[1002 ms] Retreived player: "Player #4"
[1002 ms] Retreived player: "Player #5"
[1002 ms] Retreived player: "Player #6"
[1002 ms] Retreived player: "Player #7"
[1003 ms] Retreived player: "Player #8"
[1003 ms] Retreived player: "Player #9"

如您所见,整个程序只用了一秒钟,全部同时被检索出来。

get_player_futures 这里是一个迭代器,遍历所有需要等待以检索玩家的期货。 futures::future::join_all 然后同时等待所有这些。您甚至可以使用join_all 的返回值来检索期货的值,但我们在这里不使用它。

我希望这能有所帮助;由于您的部分问题不连贯,因此很难找到答案。

【讨论】:

    猜你喜欢
    • 2017-06-08
    • 2014-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-01
    • 2016-07-23
    • 2020-01-16
    • 2021-12-24
    相关资源
    最近更新 更多