异步的生态系统

Rust 目前只提供了编写异步代码的基础必要的功能。重要的是,标准库尚未提供像 执行器(executors)、任务(tasks)、响应器(reactors)、组合器(combinators) 与低级 I/O 的功能和特征。目前,是由社区提供的异步生态来填补这些空白的。

Rust 异步基础团队,希望在 Async Book 中扩展更多的示例来涵盖多种运行时。 如果你有兴趣为这个项目做贡献,可以通过 Zulip 联系我们。

异步运行时

异步运行时,是用来执行异步程序的库。运行时通常由一个响应器及一个或多个执行器组成。 响应器为外部事务提供订阅机制,如异步 I/O、进程间通信和计时器。 在一个异步运行时里,订阅者通常是代表低级 I/O 操作的 futures。 执行器则负责任务的调试和执行。 它们会保持追踪在运行或中断的任务,当任务可取得进展(就绪)时唤醒它们, 促使(通过 poll)futures 完成。 “执行器”和“运行时”这两个词,通常我们可能互换使用。 这里,我们使用“生态系统”这个词来形容一个与其兼容的特征和特性组合在一起的运行时。

社区提供的异步箱(Crates)

Futures

futures crate 提供了在编写异步代码时实用的特征和方法。 它提供了 StreamSinkAsyncReadAsyncWrite 特征, 及实用工具如组合器。而这些实用工具和特征,也许最终会添加到标准库里!

futures 有自己的执行器,但没自己的响应器,所以它不支持计时器 和异步 I/O futures 的执行。 所以,它不是一个完整的运行时。 通常我们会选择 futures 中的实用工具,并配合其它 crate 中的执行器来使用。

受欢迎的异步运行时

因为标准库中不提供异步运行时,所以并没有所谓的官方推荐。 下面的这些 crates 提供了一些受大家喜爱的运行时。

  • Tokio:一个受欢迎的异步生态系统,提供了 HTTP,gRPC 及追踪框架。
  • async-std:一个为标准库提供异步功能组件的 crate。
  • smol:一个小且简洁实用的异步运行时。 提供了可用来装饰结构体,如 UnixStreamTcpListenerAsync 特征。
  • fuchsia-async: Fuchsia OS 中使用的执行器。

确定生态系统兼容性

并非所有的异步程序、框架和库都互相兼容,不同的操作系统和架构也是如此。 大部分的异步代码都可在任何生态系统中运行,但一些框架和库只能在特定的生态上使用。 生态系统的限制性不总被提及、记录在案,但有几个经典法则可帮助我们, 来确定库、特征和方法是否依赖于特定的生态系统。

任何包括异步 I/O、计时器、跨进程通信或任务交互的异步代码, 通常依赖于一个特殊的执行器或响应器。 而其它异步代码,如异步表达式、组合器、同步类型和流,一般是独立于生态系统的, 当然前提是其内部包含的 futures 也得是独立于生态系统存在的。 在开始一个项目之前,建议首先对使用到的异步框架、库做一个调查, 来确保你选择的运行时对它们有着良好的兼容性。

请注意,Tokio 使用 mio 作为响应器,且定义了自己的异步 I/O 特征, 包括 AsyncReadAsyncWrite。就其本身而言,它不兼容 async-stdsmol, 因为后者依赖于 async-executor crate, 且在 futures 中定义了自己的 AsyncReadAsyncWrite 特征。

有时,你可以通过一个兼容层来解决运行时的冲突问题,使用这个兼容层, 你可以在一个运行时里编写调用其它的运行时的代码。比如, async_compat crate 提供了一个可在 Tokio 和其它运行时之间使用的兼容层。

由库提供的异步 APIs 不应该依赖于某个特定的执行器或响应器, 除非它们自己来生成任务,或者定义了自己的异步 I/O 或计时器 futures。 理想情况下,应该只有二进制程序负责任务的调度与运行。

单线程与多线程执行器

异步执行器可以是单线程或多线程的。例如, async-executor 箱就提供了用于单线程的 LocalExecutor 和多线程的 Executor

多线程执行器可同时驱使多个任务取得进展。在有很多任务的工作负载上, 它可极大地提升运行速度,但在任务间同步数据也需付出更高的代价。 当你在单线程和多线程运行时之间做选择时,建议首先去测试,确认下, 不同选择下带来的应用的性能差别。

任务既可以在创建它们的线程上运行,也可选择让之在其它的线程上运行。 通常,异步运行时会提供在其它的线程上生成任务的方法。 即使任务在其它的线程上运行,它们仍需是非阻塞的。为了在多线程执行器上调度任务, 这些任务必须实现 Send 特征。一些运行时提供生成 non-Send 任务的功能, 这样可确保每个任务都将只在生成它的线程上运行。 它们可能还会提供,将阻塞任务生成到专有线程上的功能, 这在需要调用其它库中的阻塞同步代码时时非常实用。