0Pricing
Learn Rust Coding · 课时

使用 Future 与任务

了解 `Future` trait,以及任务如何在异步运行时环境中进行调度和管理。

使用 Future 与任务 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 3 节课,共 3 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 3 节课。

什么是 Rust Future?

在异步 Rust 中,Future是一种 trait,表示可能在某个时刻完成的异步计算。您可以把它理解为对某个尚未准备好的值的承诺。

  • 它是异步 Rust 的核心构建块。
  • Rust 中的async fn实际上会返回一个实现了Future trait 的匿名类型。
  • 该值不会立即计算出来,而是在执行器对Future进行“轮询”时计算。

Future 如何取得进展

Future不会自行运行。一个执行器(例如 Tokio 运行时)会反复对它进行“轮询”,以检查它是否取得了进展或已经完成。

  • 被轮询时,Future会返回Poll::Pending(尚未完成)或Poll::Ready(T)(已完成,这是结果)。
  • 如果返回Pending,执行器就会知道稍后在相关事件发生时再次轮询它(例如 I/O 完成时)。
  • 正是这种轮询机制,让许多异步操作能够在单个线程上并发运行。

您的 async 代码就是一个 Future

当您编写async fn时,Rust 会将其转换为一个实现了Future trait 的状态机。实际计算只有在对返回的Future进行轮询时才会开始。

让我们来看一个简单的async函数。它要等到执行器等待它之后才会运行。

async fn say_hello() -> String {
    "Hello from a Future!".to_string()
}

#[tokio::main]
async fn main() {
    let future = say_hello(); // This doesn't run the function yet!
    println!("Future created, but not awaited.");
    // To run it, an executor needs to poll it, often via .await
    // let result = future.await;
    // println!("{}", result);
}

Future 会变成任务

Future是异步计算的定义,而任务则是由执行器驱动并完成这一Future的活动实例。

  • 当您告诉执行器(例如 Tokio)运行一个Future时,执行器会将它封装在一个任务中。
  • 随后,执行器会管理这个任务,并在它准备好取得进展时对其进行轮询。
  • 任务是异步运行时调度并并发执行的工作单元。

生成任务以实现并发

要让一个Future与其他代码并发运行,您需要将它“生成”到 Tokio 运行时中。这会创建一个由运行时管理的新任务。

tokio::spawn函数接收一个Future并返回一个JoinHandle,您可以使用它等待任务完成并获取任务结果。

async fn perform_task(id: u8) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    format!("Task {} finished!", id)
}

#[tokio::main]
async fn main() {
    println!("Main started.");
    let handle = tokio::spawn(async {
        perform_task(1).await
    });
    println!("Task 1 spawned.");
    // We will await 'handle' in the next scene to get the result.
    // let result = handle.await.unwrap();
    // println!("{}", result);
}

获取任务结果

tokio::spawn返回的JoinHandle本身就是一个Future。您可以对这个句柄使用.await,等待生成的任务完成并获取其返回值。

如果生成的任务发生恐慌,等待其JoinHandle将返回一个错误。

async fn perform_task(id: u8) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
    format!("Task {} finished!", id)
}

#[tokio::main]
async fn main() {
    println!("Main started.");
    let handle = tokio::spawn(async {
        perform_task(1).await
    });
    println!("Task 1 spawned.");
    let result = handle.await.unwrap(); // Await the handle to get the result
    println!("{}", result);
    println!("Main finished.");
}

多个并发任务

当您并发运行多个操作时,任务和tokio::spawn的强大之处才真正显现出来。Tokio 运行时会在各个任务准备就绪时高效地在它们之间切换。

这样,即使只有一个线程,您的程序也能同时推进许多工作,而不会发生阻塞。

async fn long_task(id: u8) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(100 + id as u64 * 50)).await;
    format!("Long task {} done!", id)
}

#[tokio::main]
async fn main() {
    println!("Main started.");
    let handle1 = tokio::spawn(long_task(1));
    let handle2 = tokio::spawn(long_task(2));
    println!("Both tasks spawned.");
    let result1 = handle1.await.unwrap();
    let result2 = handle2.await.unwrap();
    println!("{}\n{}", result1, result2);
    println!("Main finished.");
}

使用 join! 等待全部任务

当您需要同时等待多个Future完成时,tokio::join!是一个实用的宏。它会并发等待所有给定的 future,并将它们的结果作为元组返回。

它类似于分别等待每个句柄,但对于数量固定的 future,通常写法更简洁。

async fn fetch_data(source: &str) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
    format!("Data from {}", source)
}

#[tokio::main]
async fn main() {
    println!("Starting data fetches...");
    let (data_a, data_b) = tokio::join!(
        fetch_data("Server A"),
        fetch_data("Database B")
    );
    println!("Fetched: {}\nFetched: {}", data_a, data_b);
    println!("All fetches complete.");
}

处理任务错误

异步操作和同步操作一样,也可能失败。Future返回Result类型来表示成功或失败,这是很常见的做法。

等待JoinHandle时请记住,结果还会被另一个Result包裹,因为任务本身可能发生恐慌。您经常会看到handle.await?(如果位于返回Result的async fn中),或者handle.await.unwrap() / .expect()。

async fn might_fail(should_fail: bool) -> Result<String, &'static str> {
    tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
    if should_fail {
        Err("Oops, something went wrong!")
    } else {
        Ok("Operation successful!".to_string())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let handle_ok = tokio::spawn(might_fail(false));
    let handle_err = tokio::spawn(might_fail(true));

    let result_ok = handle_ok.await??; // Await handle, then await inner Result
    println!("{}", result_ok);

    match handle_err.await? { // Await handle, then handle inner Result
        Ok(val) => println!("{}", val),
        Err(e) => eprintln!("Error: {}", e),
    }
    Ok(())
}

理解 Future 和任务

您已经学习了 Future 作为计算,以及任务作为其执行实例的概念。关于tokio::spawn和JoinHandle,下面哪项说法是正确的?

回顾:Future 和任务

做得很好!您现在已经掌握了 Rust 异步生态系统中Future和任务的核心概念:

  • Future是一种 trait,表示最终会产生一个值的异步计算。
  • async fn会编译为实现了Future trait 的类型。
  • 任务是Future的一个实例,由执行器(例如 Tokio)主动管理并轮询。
  • tokio::spawn用于创建新任务,并返回一个JoinHandle。
  • 您可以对JoinHandle使用.await,以获取生成任务的结果。

这些构建块对于编写高效、非阻塞的 Rust 应用至关重要!

常见问题解答

「使用 Future 与任务」课时是免费的吗?

是的 — 「使用 Future 与任务」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 3 节课。

「使用 Future 与任务」这节课中我会学到什么?

了解 `Future` trait,以及任务如何在异步运行时环境中进行调度和管理。 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Learn Rust Coding 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 3 节。

「使用 Future 与任务」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Learn Rust Coding 课中编写并运行代码吗?

能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 异步编程与 Async/Await 入门
  2. 使用 Tokio 构建异步应用
  3. 使用 Future 与任务
← 返回 Learn Rust Coding