异步函数

迄今为止,你编写的全部函数和方法都是即时执行的。除非你调用它们,否则什么都不会发生。但一旦调用了,它们就会运行至完成:它们会做所有的工作,然后返回输出结果。

有时这并非理想情况。例如,如果你正在编写一个HTTP服务器,可能会有很多等待的情况:等待请求体到达、等待数据库响应、等待下游服务回复等。

如果在等待时可以做些其他事情会怎样?如果你可以选择中途放弃计算会怎样?如果你可以选择优先处理另一个任务而非当前任务会怎样?

这就是异步函数发挥作用的地方。

async fn

你可以使用async关键字来定义一个异步函数:

#![allow(unused)]
fn main() {
use tokio::net::TcpListener;

// 这个函数是异步的
async fn bind_random() -> TcpListener {
    // [...]
}
}

如果你像调用普通函数那样调用bind_random会发生什么?

#![allow(unused)]
fn main() {
fn run() {
    // 调用 `bind_random`
    let listener = bind_random();
    // 现在怎么办?
}
}

什么也不会发生!当你调用bind_random时,Rust并不会开始执行它,甚至不会作为一个后台任务来启动(这可能基于你在其他语言中的经验)。Rust中的异步函数是惰性的:它们直到你明确要求它们执行才开始做任何工作。使用Rust的术语来说,我们说bind_random返回了一个未来,这是一种代表可能稍后完成的计算的类型。它们之所以称为“未来”,是因为它们实现了Future特质,我们将在本章后面详细探讨这个接口。

.await

让异步函数执行一些工作的最常见方法是使用.await关键字:

#![allow(unused)]
fn main() {
use tokio::net::TcpListener;

async fn bind_random() -> TcpListener {
    // [...]
}

async fn run() {
    // 调用 `bind_random` 并等待它完成
    let listener = bind_random().await;
    // 现在 `listener` 已经准备好了
}
}

.await直到异步函数运行完成——例如,上述示例中直到TcpListener被创建——才会将控制权交还给调用者。

运行时

如果你感到困惑,这是很正常的!我们刚刚说过异步函数的优点之一是它们不会立即做所有工作。然后我们介绍了.await,它要等到异步函数完成才会返回。我们是不是又重新引入了我们试图解决的问题?意义何在?

不尽然!在你调用.await时,幕后发生了许多事情!你将控制权交给了一异步运行时,也称为异步执行器。执行器是魔法发生的地点:它们负责管理你所有的正在进行的任务。具体来说,它们平衡两个不同的目标:

  • 进展:确保任务在可能时取得进展。
  • 效率:如果一个任务在等待某事,它们尽量确保另一个任务可以在此期间运行,充分利用可用资源。

无默认运行时

Rust在异步编程方面采取的方法相当独特:没有默认运行时。标准库不附带运行时。你需要自己引入!

在大多数情况下,你会从生态系统中选择一个可用的选项。有些运行时设计得广泛适用,对大多数应用程序都是坚实的选择。tokioasync-std属于这一类。其他运行时则针对特定用例进行了优化,例如,embassy针对嵌入式系统。

在整个课程中,我们将依赖于tokio,这是Rust中通用异步编程最受欢迎的运行时。

#[tokio::main]

你的可执行文件的入口点,main函数,必须是一个同步函数。你应该在这里设置并启动你选择的异步运行时。

大多数运行时都提供了一个宏来简化这个过程。对于tokio,它是tokio::main

#[tokio::main]
async fn main() {
    // 你的异步代码放这里
}

这展开为:

fn main() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(
        // 你的异步函数放这里
        // [...]
    );
}

#[tokio::test]

测试也是如此:它们必须是同步函数。每个测试函数都在自己的线程中运行,如果你需要在测试中运行异步代码,你需要负责设置并启动异步运行时。tokio提供了一个#[tokio::test]宏来简化这一过程:

#![allow(unused)]
fn main() {
#[tokio::test]
async fn my_test() {
    // 你的异步测试代码放这里
}
}