参考博客:https://hegdenu.net/posts/understanding-async-await-1/

    不 await,为什么任务什么都不做
    1. tokio::time::sleep(std::time::Duration::from_millis(100));
    2. // 这不会 sleep。编译器会立即警告你这行不通
    3. = note: futures do nothing unless you `.await` or poll them
    4. // 会 sleep
    5. tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    通常直接调用一个函数它就会执行,为什么 async 函数之后,为什么直接调用该 async 函数就不会直接执行了呢?将从一个最简单的 async 函数开始,它不做任何 async 操作
    1. async fn hello(name: &'static str) {
    2. println!("hello, {name}!");
    3. }
    然后我们可以在合适的异步上下文中调用这个函数
    1. #[tokio::main]
    2. async fn main() {
    3. hello("world").await;
    4. }
    5. // hello, world!
    但是这个函数到底是做什么的。我们知道如果移除 .await,什么都不会发生。但是为什么呢?让我们来书写我们自己的 future。具体来说,我们将实现 trait std::future::future。什么是 future 1. 一个 future 代表一个异步计算,你可以持有它,直到它的操作完成。然后你就可以访问操作完成的结果 2. 你需要直到的是,你必须把 future 给到异步运行时,然后 .await 去用它,然后运行时返回结果 3. 现在让我们写一个简单的异步函数作为一个 future,该 future 具有多个状态,实际上 future 是一个状态机。该状态机通过其状态来被异步运行时进行驱动 4. 通过最少的状态示例,即包含 2 个状态,<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 函数的参数
    1. enum Hello {
    2. Init {name: &'static str},
    3. Done,
    4. }
    因为 future 是状态机,可以画出其状态转换图

    异步理解之路一 - 图1

    1. 简而言之,Hello 枚举被构造成 Init 状态
    2. 调用 poll() 将其转换为 Done 状态
    3. 对象现在只能被丢弃(drop
    4. 这可能还没有意义。但它应该有助于理解代码。这还不是 future,所以我们不能 await。为了解决这个问题,我们需要实现 Future trait
    只看 std::future::future trait 的简单部分:https://doc.rust-lang.org/std/future/trait.Future.html# 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 时还需要其他东西,所以现在也先跳过它
    1. pub enum Poll<T> {
    2. Ready(T),
    3. Pending,
    4. }
    1. 当 future 有一个值要返回时,它返回 Ready(T),这里,TFuture 的关联类型 Output
    我们实际上也不需要一个值,所以如果你不理解 T,不用担心。跳过困难的部分会让事情变得更容易。接下来看下如何实现 poll 方法
    1. impl Future for Hello {
    2. type Output = ();
    3. fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
    4. match *self {
    5. Hello::Init { name } => println!("hello, {name}!"),
    6. Hello::Done => panic!("Please stop polling me!"),
    7. };
    8. *self = Hello::Done;
    9. Poll::Ready(())
    10. }
    11. }
    1. 如果我们处于初始状态 Init,那么输出 hello, {name} !这是 async 函数的主体
    2. 如果我们处于完成状态,我们就会恐慌
    3. 在 match 语句之后,我们将状态设置为 Done。最后,我们返回 Ready(())。记住,一个不返回任何东西的函数,实际上返回的是单位类型 ()
    稍后,我们将看到如何使用我们的新 future,但是先看看关于 pending(待定,挂起)的话题
    1. This future is very simple,It will become ready on the first poll
    2. 但如果事实并非如此呢?这就是 Poll::Pending 使用的地方,稍后我们将讨论如何使用 Pending
    稍等,那恐慌 panic 怎么办?future 是一个“一次性”的对象,一旦它完成,返回 Ready(T),它就不能再被调用了。这在该特性文档的 panic 部分中有描述(https://doc.rust-lang.org/std/future/trait.Future.html#panics),The trait doesn’t require that the future panic

    我们需要构建新的 future 来使用它,让我们把它封装在一个函数中,就像我们的 async 函数一样

    1. fn hello(name: &'static str) -> impl Future<Output = ()> {
    2. Hello::Init { name }
    3. }
    1. 关于这个函数,我们注意到的第一件事是它没有标记为 async,因为我们返回的是“手工制作”的 future,所以不能使用 async 关键字,而是返回 impl Future
    2. 那么函数体呢?构造枚举的 Init 变体并返回它。现在开始清楚了,如果不 await,为什么 async 函数什么也不做。我们什么都没做!只是构造一个对象,不会运行其他东西。所以 call our future,我们不能调用 poll(),我们没有 Context 传递给它。我们可以创建一个上下文,但那是另一个故事了。记住,我们想了解 async/await 是如何为用户工作的,而不是为异步运行时工作的
    1. use std::future::Future;
    2. use std::task::{Context, Poll};
    3. use std::pin::Pin;
    4. enum Hello {
    5. Init {name: &'static str},
    6. Done,
    7. }
    8. impl Future for Hello {
    9. type Output = ();
    10. fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
    11. match *self {
    12. Hello::Init { name } => println!("hello, {name}!"),
    13. Hello::Done => panic!("Please stop polling me!"),
    14. };
    15. *self = Hello::Done;
    16. Poll::Ready(())
    17. }
    18. }
    19. fn hello(name: &'static str) -> impl Future<Output = ()> {
    20. Hello::Init { name }
    21. }
    22. fn main() {
    23. let myfuture = hello("hbj");
    24. // 不能调用 poll 方法,只是构造了一个有 future 特征的对象
    25. // myfuture.poll();
    26. }
    1. 幸运的是,await 关键字在“手工制作”的 futures 中工作得很好。毕竟,这是编译器在底层创建的东西。因此,让我们 await 我们的 future
    1. use std::future::Future;
    2. use std::task::{Context, Poll};
    3. use std::pin::Pin;
    4. enum Hello {
    5. Init {name: &'static str},
    6. Done,
    7. }
    8. impl Future for Hello {
    9. type Output = ();
    10. fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
    11. match *self {
    12. Hello::Init { name } => println!("hello, {name}!"),
    13. Hello::Done => panic!("Please stop polling me!"),
    14. };
    15. *self = Hello::Done;
    16. Poll::Ready(())
    17. }
    18. }
    19. fn hello(name: &'static str) -> impl Future<Output = ()> {
    20. Hello::Init { name }
    21. }
    22. // fn main() {
    23. // let myfuture = hello("hbj");
    24. // // 不能调用 poll 方法
    25. // myfuture.poll();
    26. // }
    27. #[tokio::main]
    28. async fn main() {
    29. hello("world").await;
    30. }
    31. PS D:\Rust\Work_Place\hello_work> cargo run
    32. Compiling hello_work v0.1.0 (D:\Rust\Work_Place\hello_work)
    33. Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.71s
    34. Running `target\debug\hello_work.exe`
    35. hello, world!
    下面是我们 Hello future 的序列图(下图就很好的反应了 hello 函数 和 main 函数之间的异步关系

    异步理解之路一 - 图2

    我们的 async main() 函数直接调用 poll() 看起来是错误的,但是请记住 main 也正在被 poll() 调用,所以它具备了在 Hello 上调用 poll() 所需的一切。特别是上下文,但我们不担心上下文