构建我们自己的 future
从上层来看,我们需要实现以下几个部分来让 future 工作:一个 runner,future trait 以及 poll type。
首先是 runner
如果没有一个方式来执行它的话,我们的 future 什么都不会做。既然我们要实现自己的 futures,因此我们也需要实现自己的 runner。在这个练习中,我们会近似的模拟异步调用,而不是真正的异步。futures 是基于拉模式,而不是推模式,这使得 futures 的抽象是零成本的,同时也意味着一旦 futures 被开始轮询,它有责任在自己准备好下次轮询的时候通知 executor。了解这个过程是怎样工作的细节对理解 futures 是如何创建并串联起来的很重要。我们的 executor 是一个很简略的实现,它只能运行一个 future,并且不能执行真正意义上的异步调用。Tokio 的文档中有更多关于 future 运行时的信息。
下面是我们对 executor 的一个简单实现:
use std::cell::RefCell;
thread_local!(static NOTIFY: RefCell<bool> = RefCell::new(true));
struct Context<'a> {
waker: &'a Waker,
}
impl<'a> Context<'a> {
fn from_waker(waker: &'a Waker) -> Self {
Context { waker }
}
fn waker(&self) -> &'a Waker {
&self.waker
}
}
struct Waker;
impl Waker {
fn wake(&self) {
NOTIFY.with(|f| *f.borrow_mut() = true)
}
}
fn run<F>(mut f: F) -> F::Output
where
F: Future,
{
NOTIFY.with(|n| loop {
if *n.borrow() {
*n.borrow_mut() = false;
let ctx = Context::from_waker(&Waker);
if let Poll::Ready(val) = f.poll(&ctx) {
return val;
}
}
})
}
run
是类型 F 的一个泛型函数,其中 F 是 future 类型。返回类型是 Output
,是在 Future
trait 中定义的关联类型,这个我们后面再看。
这个函数的内容是对真正 runner
所做工作的模拟。它会一直循环直到收到 future 准备好再次轮询的通知。当 future 准备好的时候,run
函数返回(这里 future 准备好被再次轮询和 future 准备好,是两个不同的概念)。Context
和 Waker
类型是对 future::task
模块中定义的同步类型的模型,原始定义可以在这里找到。这们在这里的定义是为了使程序可以通过编译,具体的讨论超出了我们这篇博客的范围,你们可以自己去研究它们是如何工作的。
Poll
像下面定义的一个简单的泛型枚举类型。
enum Poll<T> {
Ready(T),
Pending
}
网友评论