七 实现Futures--主要例子
我们将用一个伪reactor和一个简单的执行器创建我们自己的Futures
,它允许你在浏览器中编辑和运行代码
我将向您介绍这个示例,但是如果您想更深入的研究它,您可以克隆存储库并自己处理代码,或者直接从下一章复制代码。
readme文件中解释了几个分支,其中有两个分支与本章相关。主分支是我们在这里经过的例子,basic_example_commented
分支是这个具有大量注释的例子
如果您希望跟随我们的步骤,可以通过创建一个新的文件夹初始化一个新的 cargo 项目,并在其中运行 cargo init。所有的一切都在main.rs文件中.
实现我们自己的Futures
让我们先从引入依赖开始:
use std::{
future::Future, pin::Pin, sync::{mpsc::{channel, Sender}, Arc, Mutex},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
thread::{self, JoinHandle}, time::{Duration, Instant}
};
执行器
执行器的责任是获取一个或多个Future
然后运行他们到完成。
执行器拿到Future
后的第一件事就是轮询它.
轮询后可以发现以下三种情况:
Future
返回Ready,然后就可以调度其他任何后续操作.这个
Future
从未被轮询过,所以传入一个Waker
,然后将它挂起这个
Future
已经被轮询过,但是返回Pending
Rust通过Waker
为Reactor和执行器提供了通信方式. reactor存储这个Waker
,然后在Future
等待的事件完成的时候调用Waker: : wake ()
,这样Future
就会被再次轮询.
我们的执行器会是这个样子:
// Our executor takes any object which implements the `Future` trait
fn block_on<F: Future>(mut future: F) -> F::Output {
// the first thing we do is to construct a `Waker` which we'll pass on to
// the `reactor` so it can wake us up when an event is ready.
let mywaker = Arc::new(MyWaker{ thread: thread::current() });
let waker = waker_into_waker(Arc::into_raw(mywaker));
// The context struct is just a wrapper for a `Waker` object. Maybe in the
// future this will do more, but right now it's just a wrapper.
let mut cx = Context::from_waker(&waker);
// So, since we run this on one thread and run one future to completion
// we can pin the `Future` to the stack. This is unsafe, but saves an
// allocation. We could `Box::pin` it too if we wanted. This is however
// safe since we shadow `future` so it can't be accessed again and will
// not move until it's dropped.
let mut future = unsafe { Pin::new_unchecked(&mut future) };
// We poll in a loop, but it's not a busy loop. It will only run when
// an event occurs, or a thread has a "spurious wakeup" (an unexpected wakeup
// that can happen for no good reason).
let val = loop {
match Future::poll(pinned, &mut cx) {
// when the Future is ready we're finished
Poll::Ready(val) => break val,
// If we get a `pending` future we just go to sleep...
Poll::Pending => thread::park(),
};
};
val
}
在本章的所有例子中,我都选择了对代码进行广泛的注释。 我发现沿着这条路走会更容易一些,所以我不会在这里重复自己的话,只关注一些可能需要进一步解释的重要方面。
现在你已经阅读了这么多关于生成器和 Pin 的内容,这应该很容易理解。 Future
是一个状态机,每一个await
点也是一个yield
点。我们可以跨越await
借用,我们遇到的问题与跨yield
借用时完全一样。
Context
只是Waker
的包装器, 至少在我写这本书的时候,它仅仅是这样。在未来,Context
对象可能不仅仅是包装一个Waker
(译者注,原文是Future,应该有误),因此这种额外的抽象可以提供一些灵活性。
正如在关于生成器的章节中解释的那样,我们使用Pin来保证允许Future
有自引用。
实现Future
Future
有一个定义良好的接口,这意味着他们可以用于整个生态系统。
我们可以将这些Future
连接起来,这样一旦leaf-future
准备好了,我们就可以执行一系列操作,直到任务完成或者我们到达另一个leaf-future
,我们将等待并将控制权交给调度程序。
我们Future
的实现是这样的:
// This is the definition of our `Waker`. We use a regular thread-handle here.
// It works but it's not a good solution. It's easy to fix though, I'll explain
// after this code snippet.
#[derive(Clone)]
struct MyWaker {
thread: thread::Thread,
}
// This is the definition of our `Future`. It keeps all the information we
// need. This one holds a reference to our `reactor`, that's just to make
// this example as easy as possible. It doesn't need to hold a reference to
// the whole reactor, but it ne