使用 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实际上会返回一个实现了Futuretrait 的匿名类型。 - 该值不会立即计算出来,而是在执行器对
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会编译为实现了Futuretrait 的类型。- 任务是
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 反馈 — 无需本地设置。
此课程中的所有课时
- 异步编程与 Async/Await 入门
- 使用 Tokio 构建异步应用
- 使用 Future 与任务