一、什么是Rust中的Futures和Async?
在Rust中,异步编程基于future(未来)的概念。一个future表示一个当前可能不可用,但将来某个时候可以获得的值。Rust中的Future
特征定义了这一概念,任何实现了该特征的类型都表示一个future。
Rust中的异步编程关键元素包括:
-
async
:async
关键字标记函数或代码块,可以被中断并稍后恢复。当你标记一个函数为async
时,它将返回一个Future
,而不是直接返回结果。 -
await
:await
关键字用于在async
函数中暂停函数的执行,直到待处理的future完成。
1.1.示例:并发地获取网页
让我们创建一个命令行程序,它并发地获取两个网页,提取它们的<title>
元素,并打印第一个完成的网页的标题。我们将使用trpl
crate,它提供了一个简化Rust异步编程的抽象,封装了像futures
和tokio
这样的常见异步库。
1.2.设置项目
首先,我们需要设置一个新的Rust项目,并将trpl
crate作为依赖项。
$ cargo new hello-async
$ cd hello-async
$ cargo add trpl
1.3.第一步:定义page_title
函数
为了从网页中获取<title>
元素,我们定义一个异步函数page_title
。该函数将使用trpl::get
方法发送HTTP GET请求到指定的URL并获取网页内容。然后,我们通过CSS选择器提取<title>
元素。
这是page_title
函数的实现:
use trpl::Html;
/// 异步函数,获取指定URL页面的<title>元素
async fn page_title(url: &str) -> Option<String> {
// 使用trpl库的get方法获取网页内容,text()方法返回网页的文本内容
let response_text = trpl::get(url).await.text().await;
// 解析HTML并查找<title>标签,返回标题内容
Html::parse(&response_text)
.select_first("title") // 使用CSS选择器查找第一个<title>元素
.map(|title| title.inner_html()) // 如果找到了<title>,返回其内部HTML内容
}
1.3.1.解释:
- 我们将函数标记为
async
,因为我们使用了异步操作,如获取URL (get(url)
) 和读取响应体 (text()
),这些都是异步操作。 get(url)
和text()
都是异步操作,所以我们使用await
等待它们完成。- 获取到响应后,我们解析HTML并使用
select_first("title")
方法查找第一个<title>
元素。 - 最后,我们返回
<title>
元素的内部HTML内容,即页面的标题,返回类型是Option<String>
。
1.4.第二步:在main
函数中调用page_title
接下来,我们需要在main
函数中调用page_title
函数。然而,Rust不允许将main
函数标记为异步函数,因此我们必须使用一个运行时来执行异步代码。我们可以使用trpl::run
函数,它初始化异步运行时并运行page_title
函数返回的future。
这是更新后的main
函数:
fn main() {
// 从命令行参数中获取两个URL
let url1 = std::env::args().nth(1).expect("Please provide the first URL");
let url2 = std::env::args().nth(2).expect("Please provide the second URL");
// 使用trpl::run运行一个异步代码块
trpl::run(async {
// 创建两个异步任务,分别获取两个URL的<title>元素
let title_fut_1 = page_title(&url1);
let title_fut_2 = page_title(&url2);
// 使用race函数并发地执行两个任务,返回第一个完成的结果
let result = trpl::race(title_fut_1, title_fut_2).await;
// 根据race函数的结果打印第一个完成的页面标题
match result {
trpl::Either::Left(Some(title)) => println!("The title for {} is: {}", url1, title),
trpl::Either::Right(Some(title)) => println!("The title for {} is: {}", url2, title),
_ => println!("Could not fetch title for one or both URLs."),
}
});
}
1.5.第三步:竞速两个URL
在这个示例中,我们从命令行传入两个URL,分别获取它们的标题,并返回第一个完成的网页。我们使用trpl::race
函数,它返回一个值,指示哪个future先完成。
1.5.1解释:
- 我们并发地调用
page_title
函数,分别创建两个future:title_fut_1
和title_fut_2
。 - 使用
trpl::race
等待哪个future先完成。它返回一个值,表示哪个future完成得更早,我们可以根据这个结果处理。 - 使用
match
语句打印第一个完成的页面的标题。如果某个页面没有<title>
标签,我们也会处理这种情况。
1.6.运行程序
要运行该程序,您需要提供两个URL作为命令行参数。下面是运行爬虫的示例:
$ cargo run -- https://www.rust-lang.org https://www.example.com
输出将显示第一个完成加载的页面的标题:
The title for https://www.rust-lang.org is: Rust Programming Language
1.7.理解Rust中的Async和Futures
在这个示例中,async
关键字将函数转换为返回Future
的函数,Future
代表一个将在未来某个时刻可用的值。这是Rust中异步编程的基本概念。
-
懒惰的Futures:在Rust中,futures是懒惰的,意味着它们不会在创建时立即执行,而是直到使用
await
显式等待它们时才会执行。这使得Rust能够优化异步任务并避免不必要的计算。 -
状态机和执行器:每个
async
函数都被Rust编译器转换为一个状态机。这些状态机允许程序在await
点暂停执行,并在未来某个时刻恢复。异步任务的执行由执行器管理,例如trpl::run
函数所提供的执行器。 -
并发性:通过让两个URL并发执行,我们利用了异步编程的优势,使程序比顺序执行更高效。
二、结论
我们成功地构建了一个简单的异步网页爬虫,能够并发地获取两个网页,并打印第一个完成的网页的标题。在这个过程中,我们学习了Rust中的异步编程的基本概念,包括futures、async/await和并发性。
通过使用trpl
crate并理解Rust的异步系统工作原理,你现在可以创建更复杂的异步应用程序,充分利用Rust的并发模型。祝编程愉快!