rust 关于.await 的疑惑

216 天前
 youngPacce

佬们,最近在学习 rust 异步编程,rust 圣经里面有这一句话: <font color=blue>总之,在 async fn 函数中使用.await 可以等待另一个异步调用的完成。但是与 block_on 不同,.await 并不会阻塞当前的线程,而是异步的等待 Future A 的完成,在等待的过程中,该线程还可以继续执行其它的 Future B ,最终实现了并发处理的效果。</font>

然后我就试了一下下面的这个用例,我预想的结果是 kitty 和 snoopy 先输出,等待 5 秒后再输出 world,但是结果是先输出 kitty,5s 后输出 snoopy,再 5s 输出 world,和文章中描述的好像不太对,请问这个 await 需要怎么理解?

use futures::executor::block_on;
use std::{thread, time};

async fn hello_world() {
    hello_cat().await;
    hello_dog().await;
    println!("hello, world!");
}

async fn hello_cat() {
    println!("hello, kitty!");
    let ten_millis = time::Duration::from_secs(5);
    thread::sleep(ten_millis);
}

async fn hello_dog() {
    println!("hello, snoopy!");
    let ten_millis = time::Duration::from_secs(5);
    thread::sleep(ten_millis);
}

fn main() {
    let future = hello_world();
    block_on(future);
}
1690 次点击
所在节点    Rust
14 条回复
aggron
216 天前
thread::sleep 是阻塞的,要用 async 版本 sleep. async_std::task::sleep(xx).await / tokio::time::sleep(xx).await
fcfangcc
216 天前
1 楼说得对,thread::sleep 的问题
youngPacce
216 天前
@aggron 谢谢我待会试试。
fcfangcc
216 天前
补充下,即使改了 thread::sleep 也不行,hello_world 需要 join hello_cat 和 hello_dog 两个 future ,不然还是会先运行 hello_cat ,再运行 hello_dog
raptium
216 天前
只改 sleep 应该也不会是你想要的效果。
.await 在逻辑上看起来还是阻塞的,只是它不会阻塞系统线程,线程还能去做别的事情。

需要你的想要效果,cat 和 dog 应该是 spawn 出来跑。这个时候可以再看看 sleep 的效果,如果用了阻塞版 sleep ,那么即使 spawn 了,kitty 和 snoopy 之间也还是要等 5s ,因为 sleep 把线程阻塞了。
i8086
216 天前
编码方式的改变,语法糖最直观就是用同步写法写异步,而无需写回调。

操作系统的改变,如:网络请求,同步写法等待响应时会阻塞线程,异步写法等待响应时不会占用线程。

最大变化在于操作系统那一块,语法糖的便利性方便我们用好异步。
jonah
216 天前
你可能需要用 https://docs.rs/tokio/latest/tokio/task/fn.spawn.html ,将 future 转成异步执行。
不然 hello_world 中的 hello_cat 和 hello_dog 两个 future 是顺序 poll 的,一个执行完再执行下一个。
tootfsg
216 天前
这是完全没有基础。
0ioaths
216 天前
将 `hello_cat` 和 `hello_dog` 作为异步任务执行就是期望的效果,以 `tokio runtime` 为例

```rust
use tokio::{runtime, time};

async fn hello_world() {
tokio::spawn(hello_cat());
tokio::spawn(hello_dog());

let five_secs = time::Duration::from_secs(5);
time::sleep(five_secs).await;
println!("hello, world!");
}

async fn hello_cat() {
println!("hello, kitty!");

let three_secs = time::Duration::from_secs(3);
time::sleep(three_secs).await;
println!("hello, kitty!2");
}

async fn hello_dog() {
println!("hello, snoopy!");
}

fn main(){
runtime::Builder::new_multi_thread()
.worker_threads(4)
.enable_all()
.build()
.unwrap()
.block_on(hello_world());
}

```

```shell
hello, kitty!
hello, snoopy!
hello, kitty!2 // after 3 sces
hello, world! // after 5 secs
```
my3157
216 天前
async runtime 其实你找个最简单的实现, 看一下就知道了, 包括 tokio smol 这些大量的代码在 reactor, 利用 epoll, kqueue, iocp, io_uring 等各个平台上提供的机制吧默认的阻塞 io 实现成 async io, 不包含这部分的话代码量很小, tokio 稍微多一些, 实现也比较复杂, 比如调度和 work stealing, 理解机制的话最简单的可能也就几百行代码, 很好理解的
nebkad
216 天前
你的疑惑不在于 await ,而是你还没理解 协程( coroutine) 和 线程 (thread) 的运行方式。
协程是对线程的分时复用,线程是操作系统提供的对 CPU 的分时复用。
Rust 的 await 是一种协程相关的关键字,你的理解是线程的工作方式。
viruscamp
215 天前
其实异步这里最好用 js 来理解 async 和 await ,js 还是一个典型的单线程 executor , 而你现在用的 futures::executor::block_on 也是单线程 executor .

想达到你要的效果,要改两点:
1. 同时发起 hello_cat().join(hello_dog()).await; // 相当于 js 的 Promise.all
2. sleep 改异步的,比如这个 https://docs.rs/futures-time/latest/futures_time/task/fn.sleep.html

如果是常用的多线程 executor 的话, thread::sleep 是可以达到你的效果的,但是错误用法。
viruscamp
214 天前
勘误:
1. 同时发起 join!(hello_cat(), hello_dog()); // 相当于 js 的 Promise.all
haharich
191 天前
这应该算是异步编程的知识了
1 、.await 不会阻塞当前线程主要是指当前线程如果此时有其他异步任务( Future )还是可以同时运行其他异步任务,并不会阻塞在只运行当前异步任务中(因为当前异步任务需要时间去完成),从现在的代码看,只有 hello_world()这个一个 Future ,所以体现不出来,假设还有一个 hello_world1()的 Future ,里面等待时间 2s ,然后不使用 block_on()去驱动这两个 Future ,而是使用 tokio 异步库的 join!()去并发驱动这两个 Future 就能看到其中一个在等待的同时可以运行另一个 Future 并输出日志(注意 thread::sleep(ten_millis) 需要换成异步然后 await 的方式,因为它是阻塞的,不然体现不出效果)
3 、所以在当前的调用栈上,用 .await 就是要等待当前异步任务完成才能执行后面的逻辑,肯定是先输出 kitty ,hello_cat()的 Future 完成后,才会执行 hello_dog 输出 snoopy ,可以简单认为这里说的不阻塞跟当前调用栈没关系

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1042318

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX