参考博客:https://hegdenu.net/posts/understanding-async-await-1/
不 await,为什么任务什么都不做通常直接调用一个函数它就会执行,为什么 async 函数之后,为什么直接调用该 async 函数就不会直接执行了呢?将从一个最简单的 async 函数开始,它不做任何 async 操作
tokio::time::sleep(std::time::Duration::from_millis(100));
// 这不会 sleep。编译器会立即警告你这行不通
= note: futures do nothing unless you `.await` or poll them
// 会 sleep
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
然后我们可以在合适的异步上下文中调用这个函数
async fn hello(name: &'static str) {
println!("hello, {name}!");
}
但是这个函数到底是做什么的。我们知道如果移除 .await,什么都不会发生。但是为什么呢?让我们来书写我们自己的 future。具体来说,我们将实现 trait std::future::future。什么是 future 1. 一个 future 代表一个异步计算,你可以持有它,直到它的操作完成。然后你就可以访问操作完成的结果 2. 你需要直到的是,你必须把 future 给到异步运行时,然后 .await 去用它,然后运行时返回结果 3. 现在让我们写一个简单的异步函数作为一个 future,该 future 具有多个状态,实际上 future 是一个状态机。该状态机通过其状态来被异步运行时进行驱动 4. 通过最少的状态示例,即包含 2 个状态,
#[tokio::main]
async fn main() {
hello("world").await;
}
// hello, world!
<font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Init</font>
和 <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Done</font>
1. Init 是 future 的初始状态
2. Done 是 future 完成后的状态
5. 通过 enum 枚举类型对 future 进行建模,在 Init 状态下,我们需要保留将传递给 async 函数的参数
因为 future 是状态机,可以画出其状态转换图
enum Hello {
Init {name: &'static str},
Done,
}
- 简而言之,Hello 枚举被构造成 Init 状态
- 调用 poll() 将其转换为 Done 状态
- 对象现在只能被丢弃(drop)
- 这可能还没有意义。但它应该有助于理解代码。这还不是 future,所以我们不能 await。为了解决这个问题,我们需要实现 Future trait
rust
pub trait Future {
type Output;
// Required method
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
1. Future trait 有一个定义输出的关联类型,这就是 async 函数的返回值。我们的 hello 函数没有返回任何东西,所以现在跳过它
2. 有一个必需的方法接受一个对 self 的可变引用 Pin<&mut Self> 和一个上下文 &mut Context<’_>,现在不需要理解 Pin,只要把它想象成任何其他的 &mut self,我们现在也不需要上下文,所以也跳过它
3. poll 方法返回一个 std::task::Poll enum(https://doc.rust-lang.org/std/task/enum.Poll.html#)。如果该方法仍有工作要做,则应该返回 Pending。在返回 Pending 时还需要其他东西,所以现在也先跳过它
pub enum Poll<T> {
Ready(T),
Pending,
}
- 当 future 有一个值要返回时,它返回 Ready(T),这里,T 是 Future 的关联类型 Output
impl Future for Hello {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
match *self {
Hello::Init { name } => println!("hello, {name}!"),
Hello::Done => panic!("Please stop polling me!"),
};
*self = Hello::Done;
Poll::Ready(())
}
}
- 如果我们处于初始状态 Init,那么输出 hello, {name} !,这是 async 函数的主体
- 如果我们处于完成状态,我们就会恐慌
- 在 match 语句之后,我们将状态设置为 Done。最后,我们返回 Ready(())。记住,一个不返回任何东西的函数,实际上返回的是单位类型 ()
- This future is very simple,It will become ready on the first poll
- 但如果事实并非如此呢?这就是 Poll::Pending 使用的地方,稍后我们将讨论如何使用 Pending
我们需要构建新的 future 来使用它,让我们把它封装在一个函数中,就像我们的 async 函数一样
fn hello(name: &'static str) -> impl Future<Output = ()> {
Hello::Init { name }
}
- 关于这个函数,我们注意到的第一件事是它没有标记为 async,因为我们返回的是“手工制作”的 future,所以不能使用 async 关键字,而是返回 impl Future
- 那么函数体呢?构造枚举的 Init 变体并返回它。现在开始清楚了,如果不 await,为什么 async 函数什么也不做。我们什么都没做!只是构造一个对象,不会运行其他东西。所以 call our future,我们不能调用 poll(),我们没有 Context 传递给它。我们可以创建一个上下文,但那是另一个故事了。记住,我们想了解 async/await 是如何为用户工作的,而不是为异步运行时工作的
use std::future::Future;
use std::task::{Context, Poll};
use std::pin::Pin;
enum Hello {
Init {name: &'static str},
Done,
}
impl Future for Hello {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
match *self {
Hello::Init { name } => println!("hello, {name}!"),
Hello::Done => panic!("Please stop polling me!"),
};
*self = Hello::Done;
Poll::Ready(())
}
}
fn hello(name: &'static str) -> impl Future<Output = ()> {
Hello::Init { name }
}
fn main() {
let myfuture = hello("hbj");
// 不能调用 poll 方法,只是构造了一个有 future 特征的对象
// myfuture.poll();
}
- 幸运的是,await 关键字在“手工制作”的 futures 中工作得很好。毕竟,这是编译器在底层创建的东西。因此,让我们 await 我们的 future
下面是我们 Hello future 的序列图(下图就很好的反应了 hello 函数 和 main 函数之间的异步关系) 我们的 async main() 函数直接调用 poll() 看起来是错误的,但是请记住 main 也正在被 poll() 调用,所以它具备了在 Hello 上调用 poll() 所需的一切。特别是上下文,但我们不担心上下文
use std::future::Future;
use std::task::{Context, Poll};
use std::pin::Pin;
enum Hello {
Init {name: &'static str},
Done,
}
impl Future for Hello {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
match *self {
Hello::Init { name } => println!("hello, {name}!"),
Hello::Done => panic!("Please stop polling me!"),
};
*self = Hello::Done;
Poll::Ready(())
}
}
fn hello(name: &'static str) -> impl Future<Output = ()> {
Hello::Init { name }
}
// fn main() {
// let myfuture = hello("hbj");
// // 不能调用 poll 方法
// myfuture.poll();
// }
#[tokio::main]
async fn main() {
hello("world").await;
}
PS D:\Rust\Work_Place\hello_work> cargo run
Compiling hello_work v0.1.0 (D:\Rust\Work_Place\hello_work)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.71s
Running `target\debug\hello_work.exe`
hello, world!