|
|
|
@ -48,7 +48,7 @@ $ cargo add trpl
|
|
|
|
|
|
|
|
|
|
</figure>
|
|
|
|
|
|
|
|
|
|
在示例 17-1 中,我们定义了一个名为 `page_title` 的函数,并使用了 `async` 关键字标记。接着我们使用 `trpl::get` 函数来获取传入的任意 URL,然后使用 `await` 关键字来等待响应。接着我们调用其 `text` 方法来获取响应的文本,这里再一次使用 `await` 关键字等待。这两个步骤都是异步的。对于 `get` 来说,我们需要等待服务器发送回其响应的第一部分,这会包含 HTTP 头(headers)、cookies 等。这部分响应可以独立于响应体发送。特别是在响应体非常大时候,接收完整响应可能会花费一些时间。因此我们不得不等待响应 *整体* 返回,所以 `text` 方法也是异步。
|
|
|
|
|
在示例 17-1 中,我们定义了一个名为 `page_title` 的函数,并使用了 `async` 关键字标记。接着我们使用 `trpl::get` 函数来获取传入的任意 URL,然后使用 `await` 关键字来等待响应。接着我们调用其 `text` 方法来获取响应的文本,这里再一次使用 `await` 关键字等待。这两个步骤都是异步的。对于 `get` 来说,我们需要等待服务器发送回其响应的第一部分,这会包含 HTTP 头(headers)、cookies 等。这部分响应可以独立于响应体发送。特别是在响应体非常大的时候,接收完整响应可能会花费一些时间。因此我们不得不等待响应 *整体* 返回,所以 `text` 方法也是异步。
|
|
|
|
|
|
|
|
|
|
我们必须显式地等待这两个 futures,因为 Rust 中的 futures 是 *惰性*(*lazy*)的:在你使用 `await` 请求之前它们不会执行任何操作。(事实上,如果你不使用一个 futures,Rust 会显示一个编译警告)这应该会让你想起[之前第十三章][iterators-lazy]关于迭代器的讨论。直到你调用迭代器的 `next` 方法(直接调用或者使用 `for` 循环或者类似 `map` 这类在底层使用 `next` 的方法)之前它们什么也不会做。对于 futures 来说,同样的基本理念也是适用的:除非你显式地请求,否则它们不会执行。惰性使得 Rust 可以避免提前运行异步代码,直到真正需要时才执行。
|
|
|
|
|
|
|
|
|
@ -75,7 +75,7 @@ $ cargo add trpl
|
|
|
|
|
|
|
|
|
|
当 Rust 遇到一个 `async` 关键字标记的代码块时,会将其编译为一个实现了 `Future` trait 的唯一的、匿名的数据类型。当 Rust 遇到一个被标记为 `async` 的函数时,会将其编译进一个拥有异步代码块的非异步函数。异步函数的返回值类型是编译器为异步代码块所创建的匿名数据类型。
|
|
|
|
|
|
|
|
|
|
因此,编写 `async fn` 就等同于编写一个返回类型的 *future* 的函数。当编译器遇到类似示例 17-1 中 `async fn page_title` 的函数定义时,它等价于以下定义的非异步函数:
|
|
|
|
|
因此,编写 `async fn` 就等同于编写一个返回类型为 *future* 的函数。当编译器遇到类似示例 17-1 中 `async fn page_title` 的函数定义时,它等价于以下定义的非异步函数:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
# extern crate trpl; // required for mdbook test
|
|
|
|
@ -173,9 +173,9 @@ The title for https://www.rust-lang.org was
|
|
|
|
|
|
|
|
|
|
最终需要某个组件来执行状态机。这就是运行时。(这也是为什么在了解运行时的时候,你可能会看到 *executors* 这个词:executor 是运行时中负责执行异步代码的部分。)
|
|
|
|
|
|
|
|
|
|
现在我们能够理解了之前示例 17-3 中为何编译器阻止我们将 `main` 本身标记为异步函数了。如果 `main` 是一个异步函数,需要有其它组件来管理 `main` futrue 返回的状态机,但是 `main` 是程序的入口点!为此我们在 `main` 函数中调用 `trpl::run`,它设置了一个运行时并运行 `async` 块返回的 future 并等待它返回 `Ready`。
|
|
|
|
|
现在我们能够理解之前示例 17-3 中为何编译器阻止我们将 `main` 本身标记为异步函数了。如果 `main` 是一个异步函数,需要有其它组件来管理 `main` futrue 返回的状态机,但是 `main` 是程序的入口点!为此我们在 `main` 函数中调用 `trpl::run`,它设置了一个运行时并运行 `async` 块返回的 future 并等待它返回 `Ready`。
|
|
|
|
|
|
|
|
|
|
> 注意:一些运行时提供了相关的宏所以你 *可以* 编写一个异步 `main` 函数。这些宏将 `async fn main() { ... }` 重写为正常的 `fn main`,执行的逻辑与我们在示例 17-5 中手动实现的一样:像 `trpl::run` 一样调用一个函数运行 future 直到结束。
|
|
|
|
|
> 注意:一些运行时提供了相关的宏,所以你 *可以* 编写一个异步 `main` 函数。这些宏将 `async fn main() { ... }` 重写为正常的 `fn main`,执行的逻辑与我们在示例 17-5 中手动实现的一样:像 `trpl::run` 一样调用一个函数运行 future 直到结束。
|
|
|
|
|
|
|
|
|
|
让我们将这些代码片段整理一下来看看如何编写并发代码,这里通过两个来自命令行的不同 URL 来调用 `page_title` 并使其相互竞争。
|
|
|
|
|
|
|
|
|
@ -197,7 +197,7 @@ The title for https://www.rust-lang.org was
|
|
|
|
|
|
|
|
|
|
> 注意:在内部 `race` 构建在一个更通用的函数 `select` 之上,你会在真实的 Rust 代码中更常遇到它。`select` 函数可以做很多 `trpl::race` 函数做不了的事,不过它也有一些额外的复杂性,所以目前我们先略过介绍。
|
|
|
|
|
|
|
|
|
|
由于任何一个 future 都可以合理地 “获胜”,所以返回 `Result` 没有意义。相反 `race` 返回了一个我们之前没有见过的类型 `trpl::Either`。`Either` 类型有点类似于 `Result`,它也有两个成员。但是不同于 `Either`,`Either` 没有内置成功或者失败的概念。相反它使用 `Left` 和 `Right` 来表示 “一个或另一个”。
|
|
|
|
|
由于任何一个 future 都可以合理地 “获胜”,所以返回 `Result` 没有意义。相反 `race` 返回了一个我们之前没有见过的类型 `trpl::Either`。`Either` 类型有点类似于 `Result`,它也有两个成员。但是不同于 `Result`,`Either` 没有内置成功或者失败的概念。相反它使用 `Left` 和 `Right` 来表示 “一个或另一个”。
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
enum Either<A, B> {
|
|
|
|
|