首页 > 解决方案 > Rust:tokio 可以理解为类似于 Javascripts 的事件循环,或者像它一样使用吗?

问题描述

我不确定它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 语句。

标签: rustasync-awaitrust-tokio

解决方案


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

首先,它main()在等待事情结束之前返回。与你的 JavaScript 代码不同,它可能在浏览器中运行,并且即使在你在控制台中键入的代码完成运行后也会运行超时,Rust 代码是一个短暂的可执行文件,它在main(). 如果可执行文件因为从main().

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

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

第一个问题可以通过在main. (这不是正确的修复,但将用于演示发生了什么。)第二个问题可以通过删除 async 块并仅传递 to 的返回值来set_timeout()修复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 的回答所示。


推荐阅读