Applied: Build an Executor

Rust 的Future是懒惰的:除非是向着’完成’这一个目标积极前进,否则他们不会做任何事情。向 Future 完成前进的一种方法是,在async函数里面,对它.await,但这只会将问题升了个级:谁来管理,从顶层 async函数返回的 Futures ?答案是:我们需要一个Future执行者(executor)。

Future executor 获取一组顶层Future,并每当Future可以前进时,通过调用poll,让它们驶向完成。通常一旦开始,executor 会poll一个 Future 。当Future表示,因wake()的调用准备好前进,会将它们先放回到一个队列,才再次poll,重复直到Future已经完成。

在本节中,我们将编写自己的简单 executor,该 executor 能够让大量顶层 Future 同时驶向完成。

在此示例中,我们依赖futures箱子,ArcWake trait 会用到,它提供了一种轻松的方法来构建Waker

  1. [package]
  2. name = "xyz"
  3. version = "0.1.0"
  4. authors = ["XYZ Author"]
  5. edition = "2018"
  6. [dependencies]
  7. futures-preview = "=0.3.0-alpha.17"

接下来,我们需要在顶部,添加以下内容src/main.rs

  1. {{#include ../../examples/02_04_executor/src/lib.rs:imports}}

我们的 executor 的工作是,将通过发送任务,在通道上运行。executor 将事件从通道中拉出,并运行它们。当一个任务准备做更多的工作(被唤醒)时,它可以安排自己重新回到通道上,以计划再次进行轮询。

在这种设计中,executor 本身仅需要任务通道的接收端。用户将获得发送端,以便他们可以生成新的 Future 。任务本身就是 Future,是可以重新计划自己的。因此,我们会将它们与一个 sender 每每存储在一起,这样,任务就可以用来让自己重新排队。

  1. {{#include ../../examples/02_04_executor/src/lib.rs:executor_decl}}

我们还向 spawner 添加一种方法,以使其易于生成新的 Future 。此方法将拿到一个 Future 类型,将其装箱,并放入 FutureObj 中,然后创建一个新类型Arc<Task>,它的内部可以在 executor 上,排队。

  1. {{#include ../../examples/02_04_executor/src/lib.rs:spawn_fn}}

要轮询 Future ,我们需要创建一个Waker。正如在唤醒章节Waker负责安排,一旦wake调用了,就再次轮询的任务。记住,Wakers 是会告诉 executor,确切的那些任务已经准备就绪,只轮询准备好前进的 Future。创建一个新的Waker最简单的方法是,通过实现ArcWake trait ,然后使用waker_ref要么.into_waker()函数,将Arc<impl ArcWake>转变成一个Waker。让我们,为我们的任务实现ArcWake,这样就可以转变为Waker,和被唤醒啦:

  1. {{#include ../../examples/02_04_executor/src/lib.rs:arcwake_for_task}}

当一个新建的Waker,从Arc<Task>而来,那么我们在它上面调用wake(),将导致Arc的一个 copy 发送到任务通道。然后,我们的 executor 需要选择任务,并进行轮询。让我们实现一下:

  1. {{#include ../../examples/02_04_executor/src/lib.rs:executor_run}}

恭喜你!我们现在有一个能工作的 Future executor。我们甚至可以使用它,来运行async/.await代码和自定义 Future ,例如,我们之前写过的TimerFuture

  1. {{#include ../../examples/02_04_executor/src/lib.rs:main}}