Rust:tokio 可以理解为类似于 Javascripts 事件循环还是可以像它一样使用?

Rust: can tokio be understood as similar to Javascripts event loop or be used like it?

我不确定 tokio 是否类似于 Javascript 中的事件循环,也是一个非阻塞运行时,或者它是否可以用于以类似的方式工作。在我的理解中,tokio 是 Rust 中未来的运行时。因此它必须实现某种用户态线程或任务,这可以通过事件循环(至少部分)来实现以安排新任务。

让我们采用以下 Javascript 代码:

console.log('hello1');
setTimeout(() => console.log('hello2'), 0);
console.log('hello3');
setTimeout(() => console.log('hello4'), 0);
console.log('hello5');

输出将是

hello1
hello3
hello5
hello2
hello4

我如何在东京做到这一点? tokio 是否打算像这样整体工作?我尝试了以下代码

async fn set_timeout(f: impl Fn(), ms: u64) {
    tokio::time::sleep(tokio::time::Duration::from_millis(ms)).await;
    f()
}

#[tokio::main]
async fn main() {
    println!("hello1");
    tokio::spawn(async {set_timeout(|| println!("hello2"), 0)}).await;
    println!("hello3");
    tokio::spawn(async {set_timeout(|| println!("hello4"), 0)}).await;
    println!("hello5");
}

输出只是

hello1
hello3
hello5

如果我将代码更改为

    println!("hello1");
    tokio::spawn(async {set_timeout(|| println!("hello2"), 0)}.await).await;
    println!("hello3");
    tokio::spawn(async {set_timeout(|| println!("hello4"), 0)}.await).await;
    println!("hello5");

输出为

hello1
hello2
hello3
hello4
hello5

但后来我不明白整个 async/await/future 功能的要点,因为那时我的“异步”set_timeout-tasks 实际上阻止了其他 println 语句..

与 JavaScript 不同,Rust 在等待未来之前不会开始执行异步函数。这意味着set_timeout(|| println!("hello2"), 0)只会创造一个新的未来。它根本不执行它。当你等待它时,它才会被执行。 .await 本质上是阻塞当前线程,直到未来完成,这不是“真正的异步应用程序”。要让你的代码像 JavaScript 一样并发,你可以使用 join! 宏:-

use tokio::join;
use tokio::time::*;

async fn set_timeout(f: impl Fn(), ms: u64) {
    sleep(Duration::from_millis(ms)).await;
    f()
}

#[tokio::main]
async fn main() {
    println!("hello1");
    let fut_1 = tokio::spawn(set_timeout(|| println!("hello2"), 0));
    println!("hello3");
    let fut_2 = tokio::spawn(set_timeout(|| println!("hello4"), 0));
    println!("hello5");

    join!(fut_1, fut_2);
}

想感受一下Promise.all可以用FuturesOrdered

更多信息:-

简而言之:是的,Tokio 的工作方式很像 JavaScript 事件循环。但是,您的第一个代码段存在三个问题。

首先,它 return 来自 main(),然后才等待事情结束。与您的 JavaScript 代码不同,它可能在浏览器中 运行s,并且 运行s 即使在您在控制台中键入的代码完成 运行ning 之后也会超时,Rust代码在一个短暂的可执行文件中,在 main() 之后终止。如果可执行文件停止 运行ning,则计划稍后发生的任何事情都不会发生,因为它 return 从 main().

编辑而来

第二个问题是调用 set_timeout() 异步函数的匿名异步块不会对其 return 值执行任何操作。 Rust 和 JavaScript 中的异步函数之间的一个重要区别是,在 Rust 中你不能只 调用 一个异步函数然后用它完成。在 JavaScript 中,异步函数 return 是一个承诺,如果您不等待该承诺,事件循环仍将在后台执行异步函数的代码。在 Rust 中,异步函数 return 是未来,但它不与任何事件循环关联,它只是为某人 运行 准备的。然后,您需要使用 .await 等待它(与 JavaScript 中的含义相同)或显式将其传递给 tokio::spawn() 以在后台执行(与调用的含义相同但不等待 JavaScript 中的函数)。您的异步块两者都不做,因此 set_timeout() 的调用是空操作。

最后,代码立即等待 spawn() 创建的任务,这首先违背了调用 spawn() 的目的 - tokio::spawn(foo()).await 在功能上等同于 foo().await 对于任何 foo().

第一个问题可以通过在 main 末尾添加一个小睡眠来解决。 (这不是正确的修复,但可以用来演示发生了什么。)第二个问题可以通过删除异步块并将 set_timeout() 的 return 值传递给 tokio::spawn() 来解决.第三个问题通过删除不必要的 .await 任务得到解决。

#[tokio::main]
async fn main() {
    println!("hello1");
    tokio::spawn(set_timeout(|| println!("hello2"), 0));
    println!("hello3");
    tokio::spawn(set_timeout(|| println!("hello4"), 0));
    println!("hello5");
    tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
}

此代码将打印“预期的”1、3、5、4、2(尽管在此类程序中不能保证顺序)。真正的代码不会以 sleep 结尾;相反,它会等待它创建的任务,如 Shivam 的回答所示。