佬们,最近在学习 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);
}
1
aggron 216 天前
thread::sleep 是阻塞的,要用 async 版本 sleep. async_std::task::sleep(xx).await / tokio::time::sleep(xx).await
|
2
fcfangcc 216 天前
1 楼说得对,thread::sleep 的问题
|
3
youngPacce OP @aggron 谢谢我待会试试。
|
4
fcfangcc 216 天前
补充下,即使改了 thread::sleep 也不行,hello_world 需要 join hello_cat 和 hello_dog 两个 future ,不然还是会先运行 hello_cat ,再运行 hello_dog
|
5
raptium 216 天前
只改 sleep 应该也不会是你想要的效果。
.await 在逻辑上看起来还是阻塞的,只是它不会阻塞系统线程,线程还能去做别的事情。 需要你的想要效果,cat 和 dog 应该是 spawn 出来跑。这个时候可以再看看 sleep 的效果,如果用了阻塞版 sleep ,那么即使 spawn 了,kitty 和 snoopy 之间也还是要等 5s ,因为 sleep 把线程阻塞了。 |
6
i8086 216 天前
编码方式的改变,语法糖最直观就是用同步写法写异步,而无需写回调。
操作系统的改变,如:网络请求,同步写法等待响应时会阻塞线程,异步写法等待响应时不会占用线程。 最大变化在于操作系统那一块,语法糖的便利性方便我们用好异步。 |
7
jonah 216 天前
你可能需要用 https://docs.rs/tokio/latest/tokio/task/fn.spawn.html ,将 future 转成异步执行。
不然 hello_world 中的 hello_cat 和 hello_dog 两个 future 是顺序 poll 的,一个执行完再执行下一个。 |
8
tootfsg 216 天前
这是完全没有基础。
|
9
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 ``` |
10
my3157 216 天前
async runtime 其实你找个最简单的实现, 看一下就知道了, 包括 tokio smol 这些大量的代码在 reactor, 利用 epoll, kqueue, iocp, io_uring 等各个平台上提供的机制吧默认的阻塞 io 实现成 async io, 不包含这部分的话代码量很小, tokio 稍微多一些, 实现也比较复杂, 比如调度和 work stealing, 理解机制的话最简单的可能也就几百行代码, 很好理解的
|
11
nebkad 216 天前 1
你的疑惑不在于 await ,而是你还没理解 协程( coroutine) 和 线程 (thread) 的运行方式。
协程是对线程的分时复用,线程是操作系统提供的对 CPU 的分时复用。 Rust 的 await 是一种协程相关的关键字,你的理解是线程的工作方式。 |
12
viruscamp 214 天前
其实异步这里最好用 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 是可以达到你的效果的,但是错误用法。 |
13
viruscamp 214 天前
勘误:
1. 同时发起 join!(hello_cat(), hello_dog()); // 相当于 js 的 Promise.all |
14
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 ,可以简单认为这里说的不阻塞跟当前调用栈没关系 |