# 8. 直接了当的Future

距离我写完《Rust速成课》系列的[最后一部](https://www.snoyman.com/blog/2018/12/rust-crash-course-07-async-futures-tokio)已经有一年了。最后一个帖子非常精彩，深入到了 async，futures 和 tokio。所有这些都在一个帖子里。这有点虐待狂的味道，在这一点上我为自己感到骄傲。

从那以后发生了很多事情。 重要的是：Future特性已移入标准库本身，并吸收了一些修改。 然后，为了更好地结合起来，有了一个新的 async/.await 语法。 在Rust中编写异步代码时，我很难高估生活质量的差异有多大。

我最近在 [FP Complete 技术网站上写了一篇文章](https://tech.fpcomplete.com/rust/pid1)，演示了 Future 和async/.await 在实践中的应用。 但是在这里，我想对表面情况进行更彻底的分析。 与第7课不同，我将跳过为什么要编写异步代码的动机，并将其分解为更易消化的部分。 像第7课一样，我将内联包含练习题方案，而不是单独发布。

在本示例中，我将使用 async-std 库而不是 tokio。 我唯一真正的原因是，在 tokio 发布了对新 async/await语法的支持之前，我开始使用async-std。 一般来说，我还不准备考虑我更喜欢哪个库。

您应该启动一个 Cargo 项目来配合。试试 cargo new --bin sleepus-interruptus。如果要确保使用相同的编译器版本，请添加一个 rust-toolchain 文件，其中包含字符串1.39.0。运行 cargo run 确保你们都准备好了。

这篇文章是基于 [FP](https://www.fpcomplete.com/rust) 完成 Rust 教学系列的一部分。 如果你在博客之外阅读这篇文章，你可以在[介绍文章的顶部](https://www.snoyman.com/blog/2018/10/introducing-rust-crash-course)找到这个系列中所有文章的链接。 也可[订阅 RSS](https://www.snoyman.com/feed/rust-crash-course) 频道。

## 中断睡眠

我想编写一个程序，将打印 Sleepus 消息 10 次，延迟0.5秒。 然后打印Interruptus 消息5次，延迟1秒。 这是一些相当简单的 Rust 代码：

```rust
use std::thread::{sleep};
use std::time::Duration;

fn sleepus() {
    for i in 1..=10 {
        println!("Sleepus {}", i);
        sleep(Duration::from_millis(500));
    }
}

fn interruptus() {
    for i in 1..=5 {
        println!("Interruptus {}", i);
        sleep(Duration::from_millis(1000));
    }
}

fn main() {
    sleepus();
    interruptus();
}
```

但是，正如我的巧妙命名所暗示的，这不是我的真正目标。 该程序同步运行两个操作，首先打印Sleepus，然后打印Interruptus。 相反，我们希望以交错的方式打印这两套语句。 这样一来，间断实际上就做了一些中断。

**练习：**&#x4F7F;用 std::thread::spawn 函数创建一个操作系统线程，使这些打印的语句交错。

解决这个问题有两种基本方法。第一种方法(也许更明显)是为每个函数生成一个单独的线程，然后等待每个函数完成：

```rust
use std::thread::{sleep, spawn};

fn main() {
    let sleepus = spawn(sleepus);
    let interruptus = spawn(interruptus);

    sleepus.join().unwrap();
    interruptus.join().unwrap();
}
```

注意两件事：

* 我们调用 spawn 为 spawn(sleepus)，而不是 spawn(sleepus()) 。前者通过函数 spawn 驱动 sleepus 来运行，后者会立即运行 sleappus() 并将其结果传递给 spawn，这不是我们想要的。
* 我在 main 函数/线程中使用 join() 等待子线程结束。因为我很懒，所以我使用 unwrap 来处理可能发生的任何错误。

另一种方法是创建一个辅助线程，然后调用主线程中的一个函数：

```rust
fn main() {
    let sleepus = spawn(sleepus);
    interruptus();

    sleepus.join().unwrap();
}
```

这样更有效率(产生线程的时间更少，用于保存线程的内存更少) ，并且没有真正的缺点。我建议走这条路。

**问题**：如果我们没有在第二个 spawn 版本中调用 join 的话，该程序的行为是什么？如果我们不调用第一个 spawn 版本的 join 会怎么样？

但是这根本不是一个异步解决问题的方法！我们有两个线程正在处理的操作系统，它们既同步运行又对睡眠进行阻塞调用。让我们建立一些直觉，来了解如何让我们的两个任务(打印“睡眠”和打印“中断”)在一个线程中更好地协同工作。

## async 介绍

我们将从最高层次的抽象开始，逐步深入到理解细节。让我们用异步方式重写我们的应用程序。将以下内容添加到 Cargo.toml：

```rust
async-std = { version = "1.2.0", features = ["attributes"] }
```

现在我们可以重写我们的应用程序如下：

```rust
use async_std::task::{sleep, spawn};
use std::time::Duration;

async fn sleepus() {
    for i in 1..=10 {
        println!("Sleepus {}", i);
        sleep(Duration::from_millis(500)).await;
    }
}

async fn interruptus() {
    for i in 1..=5 {
        println!("Interruptus {}", i);
        sleep(Duration::from_millis(1000)).await;
    }
}

#[async_std::main]
async fn main() {
    let sleepus = spawn(sleepus());
    interruptus().await;

    sleepus.await;
}
```

让我们从头到尾来看看这些变化：

* 我们从 async\_std::task 获取它们，而不是从 std::thread 获取 sleep 和 spawn
* 现在 sleepus 和 interruptus 前带上了 async
* 在调用 sleepus 后，我们有一个.await。请注意，这不是 .await() 方法调用，而是新的语法
* 在 main 函数中有一个新属性 #\[async\_std::main]
* 我们现在传入 spawn(sleepus()) 而不是 spawn(sleepus)，而是传入函数本身，然后立即运行该函数并将其结果传递给 spawn&#x20;
* 现在，对 interruptus() 的之后调用 .await
* 我们使用.await 语法来代替 JoinHandle 上的 join()

**练习：**&#x5728;您自己的机器上运行此代码，并确保所有代码都按预期编译和运行。然后尝试撤销上面列出的一些更改，看看是什么生成了编译器错误，以及是什么产生了不正确的运行时行为。

这看起来像是一大堆更改。 但实际上，我们的代码在结构上与以前的版本几乎相同，这是对 async/.await 语法的真实证明。 一切都按照我们希望的方式在表面下工作: 一个单独的操作系统线程进行非阻塞调用。

让我们来分析一下这些修改的实际含义。

## async 函数

将 async 添加到函数定义的开头可以做三件事：

* 它允许你在内部使用 .await 语法，我们稍后会解释它的含义。
* 它修改了函数的返回类型。 async fn foo ()-> Bar 实际上返回 impl std::Future::Future\<Output = Bar> 。
* 自动将结果值包装在新的Future中。 稍后我们会证明这一点更好。

让我们稍微解释一下第二点。在标准库中定义了一个叫做 Future 的特性。它有一个关联的 Output 类型。这个 trait 的意思是： 我保证，当我完成的时候，我会给你一个类型 Output 的值。例如，您可以设想一个异步 HTTP 客户端，它看起来像这样：

```rust
impl HttpRequest {
    fn perform(self) -> impl Future<Output=HttpResponse> { ... }
}
```

发出该请求将需要一些非阻塞 I/O。 在这些情况发生时，我们不想阻塞调用线程。 但是我们确实希望以某种方式获得最终的响应。

稍后我们将更直接地讨论 Future 值。目前，我们将继续使用高级 async/.await 语法。

**练习：**&#x901A;过修改其结果类型，将sleepus的签名重写为不使用async关键字。请注意，当您获得正确的类型时，代码将不会编译。注意你得到的错误信息。

async fn sleepus() 的结果类型是隐含的单元值 ()。因此，我们 Future 的 Output 应该是单元的。这意味着我们需要把我们的签名写成：

```rust
fn sleepus() -> impl std::future::Future<Output=()>
```

然而，只有这样的改变，我们才能得到下面的错误消息:

```rust
error[E0728]: `await` is only allowed inside `async` functions and blocks
 --> src/main.rs:7:9
  |
4 | fn sleepus() -> impl std::future::Future<Output=()> {
  |    ------- this is not `async`
...
7 |         sleep(Duration::from_millis(500)).await;
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only allowed inside `async` functions and blocks

error[E0277]: the trait bound `(): std::future::Future` is not satisfied
 --> src/main.rs:4:17
  |
4 | fn sleepus() -> impl std::future::Future<Output=()> {
  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::future::Future` is not implemented for `()`
  |
  = note: the return type of a function must have a statically known size
```

第一条消息非常直接：您只能在异步函数或块内使用.await语法。 我们还没有看到异步块，但这听起来像是：

```rust
async {
    // async noises intensify
}
```

第二个错误消息是第一个错误消息的结果: async 关键字导致返回类型为 impl Future。如果没有这个关键字，我们的 for 循环计算结果为 () ，这不是 impl Future。

**练习：**&#x4FEE;正编译器错误，在圆角函数中引入异步块。不要在函数签名中添加异步，继续使用 impl Future。

用异步块包装整个函数体解决了这个问题：

```rust
fn sleepus() -> impl std::future::Future<Output=()> {
    async {
        for i in 1..=10 {
            println!("Sleepus {}", i);
            sleep(Duration::from_millis(500)).await;
        }
    }
}
```

## .await 1分钟

也许我们不需要所有这些 async/.await 垃圾。 如果我们取消对 sleepus中的 .await 用法的调用，该怎么办？ 也许令人惊讶的是，它编译了，尽管确实给了我们不祥的警告：

```rust
warning: unused implementer of `std::future::Future` that must be used
 --> src/main.rs:8:13
  |
8 |             sleep(Duration::from_millis(500));
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_must_use)]` on by default
  = note: futures do nothing unless you `.await` or poll them
```

我们创建了 Future 的值，但并没有使用它。当然，如果你看看我们程序的输出，你就会明白编译器的意思:

```rust
Interruptus 1
Sleepus 1
Sleepus 2
Sleepus 3
Sleepus 4
Sleepus 5
Sleepus 6
Sleepus 7
Sleepus 8
Sleepus 9
Sleepus 10
Interruptus 2
Interruptus 3
Interruptus 4
Interruptus 5
```

我们所有的 Sleepus 消息都会立即打印输出。 有趣！ 问题在于，对 sleep 的调用实际上不再使我们当前的线程进入睡眠状态。 相反，它生成一个实现Future的值。当这个承诺最终实现的时候，我们知道延迟已经发生了。但在我们的案例中，我们只是简单地忽略了 Future，因此从来没有真正地延迟过。

为了了解 .await 语法的作用，我们将直接使用 Future 值来实现我们的功能。 让我们从去掉 async 块开始。

## 删除 async 阻塞

如果我们删除 async 块，我们会得到这样的代码：

```rust
fn sleepus() -> impl std::future::Future<Output=()> {
    for i in 1..=10 {
        println!("Sleepus {}", i);
        sleep(Duration::from_millis(500));
    }
}
```

这给出了一个我们之前看到的错误消息：

```rust
error[E0277]: the trait bound `(): std::future::Future` is not satisfied
 --> src/main.rs:4:17
  |
4 | fn sleepus() -> impl std::future::Future<Output=()> {
  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::future::Future` is not implemented for `()`
  |
```

这是有意义的:  for 循环的计算结果为 () ，而 ()  未实现 Future。解决这个问题的一种方法是在 for 循环之后添加一个表达式，计算结果为实现 Future 的内容。我们已经知道这样一件事: sleep。

**练习：**&#x8C03;整sleepus函数，使其编译。

```rust
fn sleepus() -> impl std::future::Future<Output=()> {
    for i in 1..=10 {
        println!("Sleepus {}", i);
        sleep(Duration::from_millis(500));
    }
    sleep(Duration::from_millis(0))
}
```

我们仍然会收到关于for循环中未使用的Future值的警告，但之后没有一个警告：该函数正在返回一个警告。但是不会得到以后的警告: 这个值是从函数返回的。但是，当然，睡0毫秒只是一种冗长的什么都不做的方式。如果有一个更明确地什么都不做的“虚拟” Future，那就好了。幸运的是，[确实有](https://docs.rs/async-std/1.2.0/async_std/future/fn.ready.html)。

**练习：**&#x5C06; for 循环后的 sleep 调用替换为 ready 调用。

```rust
fn sleepus() -> impl std::future::Future<Output=()> {
    for i in 1..=10 {
        println!("Sleepus {}", i);
        sleep(Duration::from_millis(500));
    }
    async_std::future::ready(())
}
```

## 实现我们自己的 Future

要进一步去除洋葱皮，让我们更加努力，而不是使用现成的函数。相反，我们将定义我们自己的结构来实现 Future。我打算叫 DoNothing。

```rust
use std::future::Future;

struct DoNothing;

fn sleepus() -> impl Future<Output=()> {
    for i in 1..=10 {
        println!("Sleepus {}", i);
        sleep(Duration::from_millis(500));
    }
    DoNothing
}
```

**练习**：该代码无法编译。 如果不查看下面的内容或询问编译器，您认为它会抱怨什么？

这里的问题是 DoNothing 没有提供 Future 实现。我们将做一些编译器驱动的开发，并让 rustc 告诉我们如何修复我们的程序。我们的第一个错误消息是：

```rust
the trait bound `DoNothing: std::future::Future` is not satisfied
```

因此，让我们添加一个 trait 实现：

```rust
impl Future for DoNothing {
}
```

失败的原因是：

```rust
error[E0046]: not all trait items implemented, missing: `Output`, `poll`
 --> src/main.rs:7:1
  |
7 | impl Future for DoNothing {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^ missing `Output`, `poll` in implementation
  |
  = note: `Output` from trait: `type Output;`
  = note: `poll` from trait: `fn(std::pin::Pin<&mut Self>, &mut std::task::Context<'_>) -> std::task::Poll<<Self as std::future::Future>::Output>`
```

我们还真的不了解 Pin<\&mut Self> 或 Context，但我们确实了解Output。 并且由于我们之前是从 Ready 调用返回 ()，所以我们在这里做同样的事情。

```rust
use std::pin::Pin;
use std::task::{Context, Poll};

impl Future for DoNothing {
    type Output = ();

    fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
        unimplemented!()
    }
}
```

哇，编译！ 当然，由于 unimplemented!() 调用，它在运行时失败：

```rust
thread 'async-std/executor' panicked at 'not yet implemented', src/main.rs:13:9
```

现在让我们尝试实现 poll。我们需要返回一个类型为 Poll \<self::output> 或 Poll <()> 的值。让我们来看看 Poll 的定义：

```rust
pub enum Poll<T> {
    Ready(T),
    Pending,
}
```

使用一些基本的推理，我们可以看到 Ready 意味着“我们的 `Future`  已经完成，然后是输出” ，而 Pending 意味着“还没有完成”。鉴于我们的NoNothing 想要立即返回 ()，我们可以在这里使用 Ready 。

**练习**：实现 poll 的工作版本。

```rust
fn poll(self: Pin<&mut Self>, _ctx: &mut Context) -> Poll<Self::Output> {
    Poll::Ready(())
}
```

恭喜，您刚刚实现了您的第一个 Future 结构体！

## The third `async` difference

记住上面我们说过让一个函数异步做第三件事：

**自动将结果值包装在一个新的 Future 中。稍后我们将更好地证明这一点。**

稍后，让我们更好地证明这一点。

让我们将sleepus的定义简化为：

```rust
fn sleepus() -> impl Future<Output=()> {
    DoNothing
}
```

编译和运行都很好。让我们尝试切换回 async 方式来编写签名：

```rust
async fn sleepus() {
    DoNothing
}
```

我们获得了一个错误：

```rust
error[E0271]: type mismatch resolving `<impl std::future::Future as std::future::Future>::Output == ()`
  --> src/main.rs:17:20
   |
17 | async fn sleepus() {
   |                    ^ expected struct `DoNothing`, found ()
   |
   = note: expected type `DoNothing`
              found type `()`
```

您可以看到，当您有一个异步函数或块时，结果将自动包装在 Future 中。因此，我们不返回 DoNothing，而是返回一个 impl Future\<Output = DoNothing> 。我们的类型需要 Output = ()。

**练习**：尝试猜测您需要向这个函数添加什么以使它能够编译。

解决这个问题非常容易：您只需将.await附加到DoNothing上：

```rust
async fn sleepus() {
    DoNothing.await
}
```

这使我们对 .await 所做的事情有了更多的直觉：它从某种程度上从DoNothing Future 中提取 () 输出。 但是，我们仍然真的不知道它是如何实现的。 让我们建立一个更复杂的 Future 来靠近。

## SleepPrint

我们将构建一个新的 Future 实现，它是：

* 睡眠一定的时间
* 然后打印一条信息

这将涉及使用[固定指针](https://doc.rust-lang.org/std/pin/index.html)。 我将不在这里描述。 固定事件的具体细节并不能很好地启发 Future 主题。 如果您希望对代码的这一部分感到满意，那么您就不会错过太多。

我们对 SleepPrint 的实现策略是用我们自己的 Future 封装一个现有的 sleep Future。由于我们不知道 sleep 调用结果的确切类型（这只是一个隐含的 future ），因此我们将使用一个参数：

```rust
struct SleepPrint<Fut> {
    sleep: Fut,
}
```

我们可以在sleepus函数中使用以下命令调用此函数：

```rust
fn sleepus() -> impl Future<Output=()> {
    SleepPrint {
        sleep: sleep(Duration::from_millis(3000)),
    }
}
```

当然，我们现在得到一个关于缺少的 Future 实现的编译器错误。所以让我们一起努力。我们的 impl 开始于:

```rust
impl<Fut: Future<Output=()>> Future for SleepPrint<Fut> {
    ...
}
```

这表示，如果 SleepPrint 包含的 sleep 值是输出类型为 () 的Future，则它是 Future。 当然，对于 sleep 函数而言，这是正确的。很好。 我们需要定义输出：

```rust
type Output = ();
```

然后我们需要一个 poll 函数：

```rust
fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
    ...
}
```

接下来的一点是包裹令人眼花缭乱的固定指针部分。 我们需要将Pin\<mut Self> 投影到 Pin＆mut Fut> 中，以便我们可以处理底层的 sleep Future。我们可以使用一个[帮助框架](https://crates.io/crates/pin-project-lite)来使它更漂亮一些，但是我们需要做一些不安全的map:

```rust
let sleep: Pin<&mut Fut> = unsafe { self.map_unchecked_mut(|s| &mut s.sleep) };
```

好了，现在最重要的是。我们拥有基本的 future，我们需要用它做些什么。我们唯一能做的就是调用 poll。poll 需要一个 \&mut Context，幸运的是我们已经提供了这个上下文。该上下文包含当前正在运行的任务的信息，因此当任务准备好时，它可以被唤醒(通过 Waker)。

注意: 在本文中，我们不打算深入探讨 Waker 是如何工作的。如果你想要一个现实生活中的例子来说明调用 Waker，我建议你阅读我在 [Rust 博客 上的 pid1](https://tech.fpcomplete.com/rust/pid1)。

目前，让我们做我们可以合理做的唯一一件事：

```rust
match sleep.poll(ctx) {
    ...
}
```

有两种可能。如果 poll 返回 Pending，则表示 sleep 还没有完成。在这种情况下，我们希望我们的 future 也表明它还没有完成。为了实现这个功能，我们只需要传播 Pending 值：

```rust
Poll::Pending => Poll::Pending,
```

但是如果 sleep 已经完成，我们就会收到一个 Ready(()) 变量。在这种情况下，终于是时候打印我们的消息，然后传播 Ready：

```rust
Poll::Ready(()) => {
    println!("Inside SleepPrint");
    Poll::Ready(())
},
```

就这样，我们已经从一个简单的 future 演变成了一个更复杂的 future。 但这是临时的。

## TwoFutures

SleepPrint是临时的：它对在Future结束后要执行的特定操作进行硬编码。让我们开始游戏，并对两个不同的Future的动作进行排序。 我们将定义一个具有三个字段的新结构：

* 第一个 future 运行
* 第二个 future 运行
* 使用一个 bool 告诉我们是否完成了第一个 future

由于 Pin 的东西会变得有点复杂，现在是时候使用帮助框架来简化我们的实现并避免不安全的阻塞了。因此，在 Cargo.toml 中添加以下内容:

```rust
pin-project-lite = "0.1.1"
```

现在，我们可以定义一个TwoFutures结构，该结构允许我们将第一个和第二个Future投影到固定的指针中：

```rust
use pin_project_lite::pin_project;

pin_project! {
    struct TwoFutures<Fut1, Fut2> {
        first_done: bool,
        #[pin]
        first: Fut1,
        #[pin]
        second: Fut2,
    }
}
```

在 sleepus 中使用这个非常简单：

```rust
fn sleepus() -> impl Future<Output=()> {
    TwoFutures {
        first_done: false,
        first: sleep(Duration::from_millis(3000)),
        second: async { println!("Hello TwoFutures"); },
    }
}
```

现在，我们只需要定义我们的 Future 实现。 容易吧？ 我们要确保 Fut1 和 Fut2 均为 future。 我们的输出将是 Fut2 的输出。 （如果需要，您还可以返回第一和第二个输出）。要使所有这些工作：

```rust
impl<Fut1: Future, Fut2: Future> Future for TwoFutures<Fut1, Fut2> {
    type Output = Fut2::Output;

    fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
        ...
    }
}
```

为了使用固定的指针，我们将获得一个新值，该值将投影所有指针：

```rust
let this = self.project();
```

这样一来，我们就可以直接与我们的三个领域进行互动。 我们要做的第一件事是检查第一个 Future 是否已经完成。 如果没有，我们将对其进行 poll。 如果 poll 测验为Ready，则我们将忽略输出，并指出第一个Future已完成：

```rust
if !*this.first_done {
    if let Poll::Ready(_) = this.first.poll(ctx) {
        *this.first_done = true;
    }
}
```

接下来，如果第一个 future 已经完成，我们希望对第二个进行 poll。如果第一个 future 没有完成，那么我们说我们正在等待：

```rust
if *this.first_done {
    this.second.poll(ctx)
} else {
    Poll::Pending
}
```

就这样，我们一起创造了两个 future，一个更大、更宏伟、更光明的 future。

**练习**：立刻去掉异步块的用法，让编译器的错误引导你。

您收到的错误消息 () 不是 future。 相反，您需要在调用 println! 之后返回 Future 值。 我们可以使用方便的 async\_std::future:: ready：

```rust
second: {
    println!("Hello TwoFutures");
    async_std::future::ready(())
},
```

## AndThen

像这样把两个任意的 future 粘在一起是很好的。但是第二个future取决于第一个future的结果就更好了。要做到这一点，我们需要一个类似 and\_then 的函数(Monads FTW 给我的 Haskell 伙伴)。我不打算在这里用实现的血淋淋的细节来烦你，但是如果你感兴趣的话，[请随意阅读 Gist](https://gist.github.com/snoyberg/7eeb5e330d9b5db9806d82c83c9d3e56)。假设你有这个方法，我们可以自己编写这个函数:

```rust
fn sleepus() -> impl Future<Output = ()> {
    println!("Sleepus 1");
    sleep(Duration::from_millis(500)).and_then(|()| {
        println!("Sleepus 2");
        sleep(Duration::from_millis(500)).and_then(|()| {
            println!("Sleepus 3");
            sleep(Duration::from_millis(500)).and_then(|()| {
                println!("Sleepus 4");
                async_std::future::ready(())
            })
        })
    })
}
```

在 Rust 1.39 和 async/.await 语法之前，基本上这就是异步代码的工作方式。 这远非完美。 除了明显的正确步骤外，它实际上不是循环。 您可以递归调用 sleepus，但是这会创建一个编译器不太喜欢的无限类型。

但是幸运的是，我们现在终于建立了足够的背景知识，可以轻松地解释 .await 语法在做什么，and\_then到底在做什么，但不必大惊小怪！

**练习：**&#x91CD;写上面的 sleepus 函数以使用 .await 代替 and\_then。

重写真的很简单，函数的主体变成了非正确的步骤，超扁平化：

```rust
println!("Sleepus 1");
sleep(Duration::from_millis(500)).await;
println!("Sleepus 2");
sleep(Duration::from_millis(500)).await;
println!("Sleepus 3");
sleep(Duration::from_millis(500)).await;
println!("Sleepus 4");
```

然后，我们还需要更改函数的签名以使用异步，或者将所有内容包装到异步块中。你说了算。

除了在此处明显改善了可读性之外，.await 还具有一些巨大的可用性改进。 一个突出的问题是它与循环绑定的容易程度。 对于较早的 future，这确实是一个痛苦。 另外，将多个await调用连在一起非常容易，例如：

```rust
let body = make_http_request().await.get_body().await;
```

不仅如此，还与 ? 完美的错误处理运算符。 上面的示例更有可能是：

```rust
let body = make_http_request().await?.get_body().await?;
```

## main 属性

最后一个谜团还没有解开，那就是 main 上那个奇怪的属性究竟是怎么回事：

```rust
#[async_std::main]
async fn main() {
    ...
}
```

我们的 sleepus 函数和 interruptus 函数实际上什么都不做。他们提供 Future 有关工作方法的说明。有些东西必须真正地执行这些动作。执行这些操作的是一个执行者。async-std 库提供了一个执行器，tokio 也是如此。 为了运行任何 Future，您需要一个执行程序。

上面的属性自动用 async-std 的执行程序包装 main 函数。然而，属性方法完全是可选的。相反，您可以使用 async *std::task::block* on。

**练习：**&#x91CD;写 main 以避免使用该属性。您需要将其从 async fn main 重写到 fn main。

由于我们在 main 主体中使用 .await，当我们简单地删除异步限定符时，会出现错误。 因此，我们需要在 main 内部使用一个异步块（或定义一个单独的辅助异步函数）。 综上所述：

```rust
fn main() {
    async_std::task::block_on(async {
        let sleepus = spawn(sleepus());
        interruptus().await;

        sleepus.await;
```

每个执行者都有能力管理多个任务。 每个任务都在努力产生单个Future的输出。 就像线程一样，您可以生成其他任务以使并发运行。 这正是我们实现所需交互的方式！

## 协作并发

一句话警告。  Future 和 async/.await  实现了一种协作并发形式。 相比之下，操作系统线程提供了抢占式并发。 重要的区别在于，在协作并发中，您必须协作。 如果您的一项任务导致了延迟，例如通过使用 std::thread::sleep或通过执行大量 CPU 计算，则不会中断该任务。

这样做的结果是您应确保在任务内部不执行阻塞调用。 而且，如果您要执行占用大量 CPU 的任务，则可能值得为其生成 OS 线程，或者至少确保执行者不会饿死您的其他任务。

## 总结

我认为.await表面下的行为并不能说明什么，但我认为准确了解此处发生的情况很有用。 特别是，了解Future值与将Future值的输出实际链接在一起之间的区别是，正确使用 async/.await 的核心。 幸运的是，编译器错误和警告在引导您朝正确的方向方面做得很好。

在下一课中，我们可以开始使用我们新获得的 Future 知识和 async/.await 语法来构建一些异步应用程序。 我们将致力于使用Tokio 0.2编写一些异步 I / O，包括网络代码。

## 练习

这里有一些可以带回家做的练习，你可以[根据 Gist 中的代码做练习](https://gist.github.com/snoyberg/f5fea804f2b6fb69ae6d1f75c8004fc5)。

1. 修改 main 函数以调用 spawn 两次而不是一次。
2. 修改 main 函数以便根本不调用 spawn。相反，[使用 join](https://docs.rs/async-std/1.2.0/async_std/future/trait.Future.html#method.join)。您需要添加一个使用 async\_std::prelude::\* ; 并将“unstable”特性添加到 Cargo.toml 中的 async-std 依赖项中。
3. 修改main函数以获得非交互行为，该程序在 Interruptus 之前多次打印Sleepus。
4. 我们仍在使 println! 打印用阻止 I/O。 再次打开“unstable”功能，然后尝试使用 async\_std::println。 您会收到一条难看的错误消息，直到您摆脱了错误为止。 尝试了解为什么会发生这种情况。
5. 编写一个函数foo，以使以下断言得以通过：assert\_eq!(42, async\_std::task::block\_on(async { foo().await.await }));


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chapin666.gitbook.io/rust-crash-course-zh/8.-ling-ren-fan-gan-de-future.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
