async/.await是 Rust 内置的语言特性,可以让我们用同步的方式去编写异步的代码。 通过async标记的语法块会被转换成实现了Future特征的状态机。 与同步调用阻塞当前线程不同,当Future执行并遇到阻塞时,它会让出当前线程的控制权,这样其它的Future就可以在该线程中运行,这种方式完全不会导致当前线程的阻塞。
在开始之前,需要先引入 futures 包。编辑 Cargo.toml 文件并添加以下内容:
[dependencies]
futures = "0.3"
使用 async
首先,使用 async fn 语法来创建一个异步函数:
async fn do_something() {
println!("go go go !");
}
需要注意,异步函数的返回值是一个 Future,若直接调用该函数,不会输出任何结果,因为 Future 还未被执行:
fn main() {
do_something();
}
运行后,go go go并没有打印,同时编译器给予一个提示:warning: unused implementer of Future that must be used,告诉我们 Future 未被使用,那么到底该如何使用?答案是使用一个执行器( executor ):
// `block_on`会阻塞当前线程直到指定的`Future`执行完成,这种阻塞当前线程以等待任务完成的方式较为简单、粗暴,
// 好在其它运行时的执行器(executor)会提供更加复杂的行为,例如将多个`future`调度到同一个线程上执行。
use futures::executor::block_on;
async fn hello_world() {
println!("hello, world!");
}
fn main() {
let future = hello_world(); // 返回一个Future, 因此不会打印任何输出
block_on(future); // 执行`Future`并等待其运行完成,此时"hello, world!"会被打印输出
}
使用.await
.await的存在支持我们使用同步的代码顺序实现了异步的执行效果,非常简单、高效,而且很好理解,也绝对不会有回调地狱的发生。
use futures::executor::block_on;
async fn hello_world() {
hello_cat().await;
println!("hello, world!");
}
async fn hello_cat() {
println!("hello, kitty!");
}
fn main() {
let future = hello_world();
block_on(future);
}
为hello_cat()添加上.await后,结果立刻大为不同:
hello, kitty!
hello, world!
总之,在async fn函数中使用.await可以等待另一个异步调用的完成。但是与block_on不同,.await并不会阻塞当前的线程,而是异步的等待Future A的完成,在等待的过程中,该线程还可以继续执行其它的Future B,最终实现了并发处理的效果。
一个例子
use futures::executor::block_on;
struct Song {
author: String,
name: String,
}
async fn learn_song() -> Song {
Song {
author: "曲婉婷".to_string(),
name: String::from("《我的歌声里》"),
}
}
async fn sing_song(song: Song) {
println!(
"给大家献上一首{}的{} ~ {}",
song.author, song.name, "你存在我深深的脑海里~ ~"
);
}
async fn dance() {
println!("唱到情深处,身体不由自主的动了起来~ ~");
}
async fn learn_and_sing() {
// 这里使用`.await`来等待学歌的完成,但是并不会阻塞当前线程,该线程在学歌的任务`.await`后,完全可以去执行跳舞的任务
let song = learn_song().await;
// 唱歌必须要在学歌之后
sing_song(song).await;
}
async fn async_main() {
let f1 = learn_and_sing();
let f2 = dance();
// `join!`可以并发的处理和等待多个`Future`,若`learn_and_sing Future`被阻塞,那`dance Future`可以拿过线程的所有权继续执行。若`dance`也变成阻塞状态,那`learn_and_sing`又可以再次拿回线程所有权,继续执行。
// 若两个都被阻塞,那么`async main`会变成阻塞状态,然后让出线程所有权,并将其交给`main`函数中的`block_on`执行器
futures::join!(f1, f2);
}
fn main() {
block_on(async_main());
}
上面代码中,学歌和唱歌具有明显的先后顺序,但是这两者都可以跟跳舞一同存在,也就是你可以在跳舞的时候学歌,也可以在跳舞的时候唱歌。如果上面代码不使用.await,而是使用block_on(learn_song()), 那在学歌时,当前线程就会阻塞,不再可以做其它任何事,包括跳舞。
原理
这两个关键字可以说是异步编程领域的标志。,但在 Rust 中这两个关键字只是起到语法糖的作用,并不是异步的核心。
async 用于快速创建 Future,不管是函数还是代码块或者lambda表达式,都可以在前面加上 async 关键字快速变成 Future 具体与哪里参考下面这个链接 rustcc.cn/article?id=…