#future #tokio #tutorial

future-by-example

Future 的常用模式示例

1 个不稳定版本

使用旧的 Rust 2015

0.1.0 2018年1月21日

#1169 in 异步

MIT/Apache

24KB

future-by-example

本文档旨在帮助读者快速开始使用 Rust 的 Future。其他一些有用的阅读材料包括

Future

Future trait 来自 futures 表示一个可能成功或失败的异步操作,无论哪种情况都会产生一个值。它类似于异步版本的 Result。本文档假定读者熟悉 Result,这在《Rust 编程语言》的第二版中有介绍。

关于 Future 最常见的问题之一似乎是,“我如何从中获取值?” 完成此操作的最简单方法是调用 wait 方法。这将在当前线程中运行 Future,在它完成之前阻塞所有其他工作。

这通常不是运行 Future 的最佳方式,因为除非 Future 完成,否则无法进行其他任何工作,这完全违背了最初使用异步编程的目的。然而,在单元测试、调试或在简单应用程序的顶层时,它可能很有用。

请参阅有关反应器部分的章节,了解运行 Future 的更好方法。

extern crate futures;
extern crate future_by_example;

fn main() {
    use futures::Future;
    use future_by_example::new_example_future;

    let future = new_example_future();

    let expected = Ok(2);
    assert_eq!(future.wait(), expected);
}

可以使用与 Result 类似的许多函数修改 Future,例如 mapmap_errthen。以下是 map

extern crate futures;
extern crate future_by_example;

fn main() {
    use futures::Future;
    use future_by_example::new_example_future;

    let future = new_example_future();
    let mapped = future.map(|i| i * 3);

    let expected = Ok(6);
    assert_eq!(mapped.wait(), expected);
}

Result 一样,两个 Future 可以使用 and_thenor_else 进行组合

extern crate futures;
extern crate future_by_example;

fn main() {
    use futures::Future;
    use future_by_example::{new_example_future, new_example_future_err, ExampleFutureError};

    let good = new_example_future();
    let bad = new_example_future_err();
    let both = good.and_then(|good| bad);

    let expected = Err(ExampleFutureError::Oops);
    assert_eq!(both.wait(), expected);
}

Future 还有很多函数在 Result 中没有对应物。因为我们正在讨论异步编程,现在我们必须选择是想顺序地(一个接一个地)运行两个独立的操作,还是同时运行(并行地)。

例如,为了获取两个独立 Future 的结果,我们可以使用 and_then 来按顺序执行它们。然而,这种策略很愚蠢,因为我们一次只在一个 Future 上取得进展。为什么不同时运行它们呢?

Future::join 创建一个新的 Future,其中包含两个其他 Future 的结果。重要的是,这两个输入 Future 可以同时取得进展。新的 Future 仅在两个输入 Future 都完成时才完成。还有 join3join4join5 用于连接更多数量的 Future

extern crate futures;
extern crate future_by_example;

fn main() {
    use futures::Future;
    use futures::future::ok;
    use future_by_example::new_example_future;

    let future1 = new_example_future();
    let future2 = new_example_future();

    let joined = future1.join(future2);
    let (value1, value2) = joined.wait().unwrap();
    assert_eq!(value1, value2);
}

join 在两个 Future 都完成时才完成,select 则返回两个 Future 中先完成的一个。这在实现超时等功能时很有用。 select2select 类似,只不过两个 Future 可以有不同的值类型。

创建一个 Future

许多库返回 Future 用于异步操作,如网络调用。有时你可能想创建自己的 Future。从头实现 Future 很困难,但还有其他创建未来(future)的方法。

你可以使用 ok 函数轻松地从已存在的值创建一个 Future。还有类似的 errresult 方法。

extern crate futures;

fn main() {
    use futures::Future;
    use futures::future::ok;

    // Here I specify the type of the error as (); otherwise the compiler can't infer it
    let future = ok::<_, ()>(String::from("hello"));
    assert_eq!(Ok(String::from("hello")), future.wait());
}

未来和类型

使用 Future 的工作往往会产生复杂类型。例如,以下表达式的完整类型实际上是

futures::Map<
  futures::Map<
    futures::Join<
      futures::FutureResult<u64, ()>,
      futures::FutureResult<u64, ()>
    >,
    [closure@src/lib.rs:...]>,
  [closure@src/lib.rs:...]
>

也就是说,对于每次转换,我们都会给我们的 Future 类型添加一个额外的层!这有时可能会令人困惑。特别是,写出类型的方式可能会很脆弱或冗长。

为了帮助 Rust 编译器进行类型推断,我们在下面指定了 expected 的类型。这比写出完整的类型要简洁得多,并且添加另一个操作不会破坏编译。

extern crate futures;

fn main() {
    use futures::future::ok;
    use futures::Future;

    let expected: Result<u64, ()> = Ok(6);
    assert_eq!(
        ok(5).join(ok(7)).map(|(x, y)| x + y).map(|z| z / 2).wait(),
        expected
    )
}

或者,我们可以使用 _ 让 Rust 编译器为我们推断类型。

extern crate futures;

fn main() {
    use futures::future::ok;
    use futures::Future;
    use futures::Map;

    let expected: Result<_, ()> = Ok(6);
    let twelve: Map<_, _> = ok(5).join(ok(7)).map(|(x, y)| x + y);
    assert_eq!(twelve.map(|z| z / 2).wait(), expected)
}

Rust 要求在函数签名中指定所有类型。

对于返回 Future 的函数,一种实现方式是在函数签名中指定完整的返回类型。然而,指定确切的类型可能会很冗长、脆弱和困难。

我们希望能够定义一个像这样的函数

fn make_twelve() -> Future<Item=u64, Error=()> {
    unimplemented!()
}

然而,编译器不喜欢这样

error[E0277]: the trait bound `futures::Future<Item=u64, Error=()>: std::marker::Sized` is not satisfied
   --> src/lib.rs:119:13
    |
119 |         let twelve = make_twelve();
    |             ^^^^^^ `futures::Future<Item=u64, Error=()>` does not have a constant size known at compile-time
    |
    = help: the trait `std::marker::Sized` is not implemented for `futures::Future<Item=u64, Error=()>`
    = note: all local variables must have a statically known size

这可以通过将返回类型包装在一个 Box 中来解决。有一天,这将通过目前尚不稳定的 impl Trait 功能以更优雅的方式解决。

extern crate futures;

fn main() {
    use futures::Future;
    use futures::future::ok;

    fn make_twelve() -> Box<Future<Item=u64, Error=()>> {

        ok(5).join(ok(7)).map(|(x, y)| x + y).boxed()
    }

    let twelve = make_twelve();
    assert_eq!(twelve.map(|z| z / 2).wait(), Ok(6))
}

与函数不同,闭包不需要在其签名中显式定义所有类型,因此不需要包裹在一个 Box 中。

extern crate futures;

fn main() {
    use futures::Future;

    let make_twelve = || {
        use futures::future::ok;

        // We don't need to put our `Future` inside of a `Box` here.
        ok(5).join(ok(7)).map(|(x, y)| x + y)
    };

    let expected: Result<u64, ()> = Ok(6);
    let twelve = make_twelve();
    assert_eq!(twelve.map(|z| z / 2).wait(), expected)
}

运行未来的更强大方式

将多个 Futures 组合成一个单独的 Future 并调用其上的 wait 方法是一个简单易行的方法,只要您一次只运行一个 Future。然而,如果您一次只运行一个 Future,可能一开始就无需使用 futures crate!futures crate 承诺能够高效地处理许多并发任务,让我们看看它是如何实现的。

tokio-core crate 包含一个名为 Core 的结构体,它可以并发运行多个 Future。调用 Core::run 来运行一个 Future 并返回其值。与 Future::wait 不同,它允许 Core 在执行 run 时,同时执行其他 Future 对象。在 Core::run 中的 Future 是主事件循环,它可以通过调用 Handle::spawn 请求运行新的 Future。请注意,通过 spawn 运行的 Future 无法返回值;它们的存在只是为了执行副作用。

extern crate futures;
extern crate tokio_core;

fn main() {
    use tokio_core::reactor::Core;
    use futures::future::lazy;

    let mut core = Core::new().unwrap();
    let handle = core.handle();
    let future = lazy(|| {
        handle.spawn(lazy(|| {
            Ok(()) // Ok(()) implements FromResult
        }));
        Ok(2)
    });
    let expected: Result<_, ()> = Ok(2usize);
    assert_eq!(core.run(future), expected);
}

许可:MIT/Apache-2.0

依赖项

~6MB
~94K SLoC