深入理解 async 相关的 traits
ch17-05-traits-for-async.md
commit 56ec353290429e6547109e88afea4de027b0f1a9
贯穿本章,我们通过多种方式使用了 Future
、Pin
、Unpin
、Stream
和 StreamExt
trait。但是直到目前为止,我们避免过多地了解它们如何工作或者如何组合在一起的细节,这对你日常的 Rust 开发而言通常是没问题的。不过有时你会遇到需要了解更多细节的场景。在本小节,我们会足够深入以便理解这些场景,并仍会将 真正 有深度的内容留给其它文档。
Future
trait
让我们以更深入地了解 Future
trait 作为开始。这里是 Rust 中其如何定义的:
#![allow(unused)] fn main() { use std::pin::Pin; use std::task::{Context, Poll}; pub trait Future { type Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>; } }
trait 定义中包含一些的新类型和我们之前没有见过新语法,所以让我们逐步详细地解析一下这个定义。
首先, Future
的关联类型 Output
表明 future 最终解析出的类型。这类似于 Iterator
trait 的关联类型 Item
。其次,Future
还有一个 poll
方法,其有一个特殊的 self
参数的 Pin
引用和一个 Context
类型的可变引用,并返回一个 Poll<Self::Output>
。稍后我们再细说 Pin
和 Context
。现在让我们专注于方法返回的 Poll
类型:
#![allow(unused)] fn main() { enum Poll<T> { Ready(T), Pending, } }
Poll
类型类似于一个 Option
。它有一个包含值的变体 Ready(T)
,和一个没有值的变体 Pending
。不过 Poll
所代表的意义与 Option
非常不同!Pending
变体表明 future 仍然还有工作要进行,所有调用者稍后需要再次检查。Ready
变体表明 future 已经完成了其工作并且 T
的值是可用的。
注意:对于大部分功能,调用者不应在 future 返回
Ready
后再次调用poll
。很多 future 在完成后再次轮询会 panic。可以安全地再次轮询的 future 会在文档中显示地说明。这类似于Iterator::next
的行为。
当你见到使用 await
的代码时,Rust 会在底层将其编译为调用 poll
的代码。如果你回头看下示例 17-4,其在一个单个 URL 解析完成后打印出页面标题,Rust 将其编译为一些类似(虽然不完全是)这样的代码:
match page_title(url).poll() {
Ready(page_title) => match page_title {
Some(title) => println!("The title for {url} was {title}"),
None => println!("{url} had no title"),
}
Pending => {
// But what goes here?
}
}
如果 future 仍然是 Pending
的话我们应该做什么呢?我们需要某种方式不断重试,直到 future 最终准备好。换句话说,我们需要一个循环:
let mut page_title_fut = page_title(url);
loop {
match page_title_fut.poll() {
Ready(value) => match page_title {
Some(title) => println!("The title for {url} was {title}"),
None => println!("{url} had no title"),
}
Pending => {
// continue
}
}
}
但是如何 Rust 将其编译为正好如此的代码的话,每一个 await
都会阻塞 -- 这与我们期望的完全不同!相反,Rust 确保循环可以将控制权交给一些可以暂停当前 future 并处理其它 future 并在之后再次检查的内容。如你所见,它就是异步运行时,这种安排和协调的工作是其主要工作之一。
在本章的开头,我们描述了等待 rx.recv
。recv
调用返回一个 future,并等待轮询它的 future。我们注意到当信道关闭时运行时会暂停 future 直到它就绪并返回 Some(message)
或 None
。随着我们对 Future
trait,尤其是 Future::poll
的理解的深入,我们可以看出其是如何工作的。运行时知道 future 返回 Poll::Pending
时没有完成。反过来说,当 poll
返回 Poll::Ready(Some(message))
或 Poll::Ready(None)
时运行时知道 future 已经完成了并继续运行。
运行时如何工作的具体细节超出了本书的范畴。不过关键在于理解 future 的基本机制:运行时轮询其所负责的每一个 future,在它们还没有完成时使其休眠。