Merge pull request #871 from Rainanxu/main

修正一些文本描述。
pull/875/head
KaiserY 1 week ago committed by GitHub
commit 3e8a306df7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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` 请求之前它们不会执行任何操作。(事实上,如果你不使用一个 futuresRust 会显示一个编译警告)这应该会让你想起[之前第十三章][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> {

@ -215,7 +215,7 @@ hi number 9 from the first task!
我们可以在代码的某处调用 `rx.close` 来手动关闭 `rx`,不过这并没有太多意义。在处理了任意数量的消息后停止可以使程序停止,但是可能会丢失消息。我们需要其它的手段来确保 `tx` 在函数的结尾 *之前* 被丢弃。
目前发送消息的异步代码块只是借用了 `tx`,因为发送消息并不需要其所有权,但是如果我们可以将 `tx` 移动move进异步代码,它会在代码块结束后立刻被丢弃。在第十三章中我们学习了如何在闭包上使用 `move` 关键字,在第十六章中,我们知道了使用线程时经常需要移动数据进闭包。同样的基本原理也适用于异步代码块,因此 `move` 关键字也能像闭包那样作用于异步代码块。
目前发送消息的异步代码块只是借用了 `tx`,因为发送消息并不需要其所有权,但是如果我们可以将 `tx` 移动move进异步代码,它会在代码块结束后立刻被丢弃。在第十三章中我们学习了如何在闭包上使用 `move` 关键字,在第十六章中,我们知道了使用线程时经常需要移动数据进闭包。同样的基本原理也适用于异步代码块,因此 `move` 关键字也能像闭包那样作用于异步代码块。
在示例 17-12 中,我们将发送消息的异步代码块从普通的 `async` 代码块修改为 `async move` 代码块。当运行 *这个* 版本的代码时,它会在发送和接收完最后一条消息后优雅地关闭。
@ -247,7 +247,7 @@ hi number 9 from the first task!
</figure>
现在我们会看到所有来两个发送 future 的消息。因为发送 future 采用了稍微不同的发送延迟,消息也会以这些不同的延迟接收。
现在我们会看到所有来两个发送 future 的消息。因为发送 future 采用了稍微不同的发送延迟,消息也会以这些不同的延迟接收。
<!-- Not extracting output because changes to this output aren't significant;
the changes are likely to be due to the threads running differently rather than

@ -254,7 +254,7 @@ received 'you'
当我们使用 `join` 系列函数和宏来 “join” future 时,我们要求它们 *全部* 结束才能继续。虽然有时我们只需要 *部分* future 结束就能继续,这有点像一个 future 与另一个 future 竞争。
在示例 17-21 中,我们再次使用 `trpl::race` 来运行 `slow``fast` 两个 future 并相互竞争。它们每一个都会在开始运行时打印一条消息,通过调用 await `sleep` 暂停一段时间,接着在其结束时打印另一条消息。然后我们将它们传递给 `trpl::race` 并等待其中一个结束。(结果不会令人意外:`fast` 会赢!)不同于我们在[第一个异步程序][async-program]中使用 `race` 的时候,这里忽略了其返回的 `Either` 实例,因为所有有趣的行为都发生在异步代码块中。
在示例 17-21 中,我们再次使用 `trpl::race` 来运行 `slow``fast` 两个 future 并相互竞争。它们每一个都会在开始运行时打印一条消息,通过调用 await `sleep` 暂停一段时间,接着在其结束时打印另一条消息。然后我们将它们传递给 `trpl::race` 并等待其中一个结束。(结果不会令人意外:`fast` 会赢!)不同于我们在[第一个异步程序][async-program]中使用 `race` 的时候,这里忽略了其返回的 `Either` 实例,因为所有有趣的行为都发生在异步代码块中。
<figure class="listing">
@ -270,7 +270,7 @@ received 'you'
请注意如果你反转 `race` 参数的顺序“started” 消息的顺序会改变,即使 `fast` future 总是第一个结束。这是因为这个特定的 `race` 函数实现并不是公平的。它总是以传递的参数的顺序来运行传递的 futures。其它的实现 *是* 公平的,并且会随机选择首先轮询的 future。不过无论我们使用的 race 实现是否公平,其中 *一个* future 会在另一个任务开始之前一直运行到异步代码块中第一个 `await` 为止。
回忆一下[第一个异步程序][async-program]中提到在每一个 await point如果被 await 的 future 还没有就绪Rust 会给运行时一个机会来暂停该任务并切换到另一个任务。反过来也是正确的Rust *只会* 在一个 await point 暂停异步代码块并将控制权交还给运行时。await points 之间的一切都是同步。
回忆一下[第一个异步程序][async-program]中提到在每一个 await point如果被 await 的 future 还没有就绪Rust 会给运行时一个机会来暂停该任务并切换到另一个任务。反过来也是正确的Rust *只会* 在一个 await point 暂停异步代码块并将控制权交还给运行时。await points 之间的一切都是同步
这意味着如果你在异步代码块中做了一堆工作而没有一个 await point则那个 future 会阻塞其它任何 future 继续进行。有时你可能会听说这称为一个 future *starving* 其它 future。在一些情况中这可能不是什么大问题。不过如果你在进行某种昂贵的设置或者长时间运行的任务亦或有一个 future 会无限持续运行某些特定任务的话,你会需要思考在何时何地将控制权交还运行时。

Loading…
Cancel
Save