Products
GG网络技术分享 2025-03-18 16:13 0
欢迎来到 Rust 异步编程!如果你正打算用 Rust 写一些异步代码,那你就来对地方了。不管你打算构建 Web 服务器,数据库,还是操作系统,这本书都能帮助你运用 Rust 的异步编程工具最大化利用你的硬件。
本书旨在提供一份全面并保持更新的 Rust 异步编程指南,主要介绍 Rust 在异步编程方面的语言特性及相关类库,适用于初学者和老司机。
开始几章主要介绍异步编程,以及在 Rust 中的特别之处。
中间几章讨论用于异步编程的关键的基础设施和控制流工具,并详细介绍了一些构建类库、应用时将性能和复用性最大化的最佳实践。
本书的最后章节介绍了 Rust 的异步生态并提供了一些实现常见功能的例子。
接下来,让我们打开 Rust 异步编程世界的大门吧!
我们喜欢 Rust,因为它能让我们写出高效、安全的代码,但为什么要异步呢?
因为异步代码能让我们在同一个系统线程上并发执行多项任务。在一个典型的多线程应用里,如果你想同时下载两个不同的网页,你必须将这两项工作分配到两个不同的线程上,像这样:
fn get_two_sites() {
// 创建两个线程分别执行各自的下载任务
let thread_one = thread::spawn(|| download("https:://www.foo.com"));
let thread_two = thread::spawn(|| download("https:://www.bar.com"));
// 等待两个线程完成任务
thread_one.join();
thread_two.join();
}
对很多应用来说这就足够了 —— 这样一来,多线程就被设计为只用来一次性执行多个不同任务。但这也带来了一些限制。在线程切换和跨线程共享数据上会产生很多额外开销。即使是一个什么都不做的线程也会用尽珍贵的系统资源,而这就是异步代码要减少的开销。我们可以使用 Rust 的 async/await! 重写上面的函数,实现执行多个任务的目标而不用创建多个线程:
async fn get_two_sites() {
// Create a two different "futures" which, when run to completion,
// will asynchronously download the webpages.
// 创建两个不同的 future,当它们被完成执行时会异步下载不同的网页
let future_one = download_async("https:://www.foo.com");
let future_two = download_async("https:://www.bar.com");
// 同时执行两个 future 使它们完成
join!(future_one, future_two);
}
总之,相比多线程实现来说,异步实现的应用具有使用更少的资源获得更高性能的潜力。线程由操作系统支持,使用它们并不需要特别的编程模型 —— 任何函数都可以创建一个线程,而调用一个使用多线程的函数就像调用一个普通函数一样简单。但是异步函数就需要语言层面或者类库层面提供特殊的支持才能工作。在 Rust 中,async fn 会创建一个异步函数,当它被调用时,会返回一个需要依次执行函数体来完成的 future 对象。
传统多线程应用也可以非常有效,Rust 的较小的内存占用以及可预测性意味着你可以做更多的事,即使不使用 async 关键字。然而,异步编程模型增长的复杂性并不总是值得的,想清楚你的应用采用简单多线程模型是否会更好仍然是很重要的。
async/.await 是 Rust 语言用于编写像同步代码一样的异步函数的内置工具。async 将一个代码块转化为一个实现了名为 Future 的特质(trait)的状态机。虽然在同步方法中调用阻塞函数会阻塞整个线程,但阻塞的 Futures 将让出线程控制权,允许其他 Futures 运行。
要创建异步函数,可以使用 async fn 语法:
async fn do_something() { ... }
async fn 返回的值是一个 Future,需要在执行者上运行才能起作用:
// `block_on` blocks the current thread until the provided future has run to
// completion. Other executors provide more complex behavior, like scheudling
// multiple futures onto the same thread.
use futures::executor::block_on;
async fn hello_world() {
println!("hello, world!");
}
fn main() {
let future = hello_world(); // Nothing is printed
block_on(future); // `future` is run and "hello, world!" is printed
}
在 async fn 中,你可以使用.await 等待另一种实现 Future 特性的类型完成,例如另一个 async fn 的输出。 与 block_on 不同,.await 不会阻止当前线程,而是异步等待 Future 完成,如果 Future 无法取得进展,则允许其他任务运行。
例如,假设我们有三个 async fn:learn_song,sing_song 和 dance:
async fn learn_song() -> Song { ... }
async fn sing_song(song: Song) { ... }
async fn dance() { ... }
一种执行 “学习”、“唱歌” 和 “跳舞” 的方法是,在执行每一项任务时阻塞:
fn main() {
let song = block_on(learn_song());
block_on(sing_song(song));
block_on(dance);
}
但是,我们使用这种方式并没有发挥出最大的性能 —— 我们只是把它们一个个执行了。很明显,我们唱歌之前必须要学会它,但是在学歌和唱歌的同时我们也是可以跳舞的。要实现这样的效果,我们可以分别创建两个 async fn 来并发地执行:
async fn learn_and_sing() {
// 在唱歌之前等待学歌完成
// 这里我们使用 `.await` 而不是 `block_on` 来防止阻塞线程,这样就可以同时执行 `dance` 了。
let song = learn_song().await;
sing_song(song).await;
}
async fn async_main() {
let f1 = learn_and_sing();
let f2 = dance();
// `join!` 类似于 `.await` ,但是可以等待多个 future 并发完成
// 如果学歌的时候有了短暂的阻塞,跳舞将会接管当前的线程,如果跳舞变成了阻塞
// 学歌将会返回来接管线程。如果两个futures都是阻塞的,
// 这个‘async_main'函数就会变成阻塞状态,并生成一个执行器
futures::join!(f1, f2)
}
fn main() {
block_on(async_main());
}
在本例中,学歌必须发生在唱歌之前,但是学习和唱歌当同时都可以跳舞。如果我们在 learn_and_sing 中使用 block_on(learn_song()) 而不是 learn_song().await 的话,它的执行将阻塞至学歌结束,就无法同时跳舞了。通过 .await 学歌这一操作,我们允许其他任务并发执行。
到目前为止你已经学会了 async/.await 的基本用法,现在我们尝试写一个例子。
让我们使用 async/ .await 构建一个 echo 服务器!
首先,请 rustup update stable 确保您的 Rust 版本是标准版本 1.39 或更新的。完成后,运行 cargo new async-await-echo 以创建新项目,然后打开生成的 async-await-echo 文件夹。
让我们在 Cargo.toml 文件中添加一些依赖项:
[dependencies]
# The latest version of the "futures" library, which has lots of utilities
# for writing async code. Enable the "compat" feature to include the
# functions for using futures 0.3 and async/await with the Hyper library,
# which use futures 0.1.
futures = { version = "0.3", features = ["compat"] }
# Hyper is an asynchronous HTTP library. We'll use it to power our HTTP
# server and to make HTTP requests.
hyper = "0.12.9"
现在我们已经完成依赖项了,让我们开始编写一些代码。我们先添加一些导入:
use {
hyper::{
// Hyper中的其他类型,用于构建HTTP
Body, Client, Request, Response, Server, Uri,
// 该函数将闭包转换为实现了Hyper的Service trait的future
// 它是从通用Request到Response的异步函数。
service::service_fn,
// 使用Hyper运行时可以运行future到完成的函数。
rt::run,
},
futures::{
// futures 0.1版本的一个扩展trait,添加了'.compat()'方法
// 允许我们在0.1版本的futures中使用'.await'语法
compat::Future01CompatExt,
// 扩展trait在futures上提供了额外的方法在
// `FutureExt` 添加了适用于所有futures的方法,
// `TryFutureExt` 给futures添加了可以放回‘Result’类型的方法
future::{FutureExt, TryFutureExt},
},
std::net::SocketAddr,
};
一旦导入完成,我们就可以开始整理样板,以便我们提供请求服务:
async fn serve_req(_req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
// 通常成功地返回一个包含友好问候的body的响应(response)
Ok(Response::new(Body::from("hello, world!")))
}
async fn run_server(addr: SocketAddr) {
println!("Listening on http://{}", addr);
// 创建绑定到提供的地址的服务器
let serve_future = Server::bind(&addr)
// 服务器请求使用 `async serve_req` 这个函数.
// `serve` 方法采取闭包操作只要求返回的值实现‘Service’这个trait,
// `service_fn` 这个函数正好返回一个实现'Service' 这个trait的值
// 该方法也是采用闭包操作,返回的是一个响应future的请求
// 为了使用Hyper的serve_req这个函数,我们必须用box把它包裹起来
// 并且用compat方法让他获得0.1futures的兼容性(Hyper还是0.1的futures,所以显得麻烦)
.serve(|| service_fn(|req|
serve_req(req).boxed().compat()
));
// 等待服务完成服务或者因为某个错误而退出
// 如果一个错误出现,输出它到stderr
if let Err(e) = serve_future.compat().await {
eprintln!("server error: {}", e);
}
}
fn main() {
// 设置地址以运行我们的套接字
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
// 调用 run_server 函数, 将返回一个future
// 与每一个`async fn`一样, 对于`run_server`做任何事情,
// 返回的future需要运行
// 额外地,我们需要将返回的future从futures0.3转换成0.1版本的future
let futures_03_future = run_server(addr);
let futures_01_future =
futures_03_future.unit_error().boxed().compat();
// 最后我们用Hyper提供的run方法运行我们的future直到完成
run(futures_01_future);
}
如果您现在 cargo run,您应该在终端上看到 "Listening on http://127.0.0.1:300" 消息。如果您在所选择的浏览器中打开该 URL,你应该看到 "hello, world!" 出现在浏览器中。恭喜!您刚刚在 Rust 中编写了第一个异步 Web 服务器。
您还可以检查请求本身,其中包含请求 URI,HTTP 版本,标头和其他元数据等信息。例如,我们可以打印出请求的 URI,如下所示:
println!("Got request at {:?}", req.uri());
你可能已经注意到我们在处理请求时还没有做任何异步 - 我们只是立即回应,所以我们没有利用 async fn 给我们的灵活性。让我们尝试使用 Hyper 的 HTTP 客户端将用户的请求代理到另一个网站,而不仅仅是返回静态消息。
我们首先解析出我们想要请求的 URL:
let url_str = "http://www.rust-lang.org/en-US/";
let url = url_str.parse::<Uri>().expect("failed to parse URL");
然后我们可以创建一个新的 hyper::Client 并使用它来发出 GET 请求,将响应返回给用户:
let res = Client::new().get(url).compat().await;
// Return the result of the request directly to the user
println!("request finished --returning response");
res
Client::get 返回 hyper::client::FutureResponse,他实现了 Future<Output = Result<Response, Error>> (或 Future<Item = Response, Error = Error> 在 futures 0.1)。当我们.await 这个 future 时,HTTP 请求已发出,当前任务被暂停,并且一旦响应可用,任务就排队等待继续。
现在,如果 cargo run 您在浏览器中打开 http://127.0.0.1:3000/foo,您将看到 Rust 主页,以及以下终端输出:
Listening on http://127.0.0.1:3000
Got request at /foo
making request to http://www.rust-lang.org/en-US/
request finished-- returning response恭喜!您刚刚完成了代理 HTTP 请求。
JavaScript事件绑定常用方法及自建函数bind()与兼容性问题解决是什么?今天我们一起了解一下。
JavaScript事件绑定常用方法如下:
1、对象.事件=函数;
它只能同时为一个对象的一个事件绑定一个响应函数
不能绑定多个,如果有多个,后面的会覆盖前面的
2、addEventListener()
此方法也可以为元素绑定响应函数
参数:
●事件的字符串(不带on)
●回调函数,事件触发时执行
●是否在捕获阶段触发事件,一般都传false
使用此方法可以为一个元素的同一事件绑定多个响应函数
当事件触发时,按绑定顺序依次执行
3、attachEvent()
IE8及以下浏览器不支持addEventListener()方法,但可以使用attachEvent()方法起到同样的效果
●参数:
事件字符串(带on)
●回调函数
此方法也可以绑定多个函数,不过函数执行顺序与addEventListener()相反
4、this问题与解决
addEventListener()中的this是绑定事件的对象
attachEvent()中的this是window
如果要解决兼容性问题则需要统一两个方法的this
这里我们用到了call()方法
call()可以用来改变函数的this
自建函数bind()
自己定义一个函数用来给一个对象绑定事件
●思路
三个参数:对象,事件,回调函数
●兼容性:
通过if判断对象是否存在addEventListener方法来区分浏览器
●this问题的解决
由于传入的回调函数是浏览器调用的,我们无法去操作,所以我们在attachEvent()不直接传入回调函数,而是先定义一个匿名函数,然后在函数内部调用回调函数,并利用call方法改变this
示例代码
<!DOCTYPEhtml>
<html>
<head>
<metacharset=\"UTF-8\">
<title></title>
<scripttype=\"text/javascript\">
window.onload=function(){
varbtn1=document.getElementById(\"btn1\");
bind(btn1,\"click\",function(){
alert(this);
});
};
//定义一个函数bind(),用来为指定元素绑定事件响应函数
/*
*参数:
*obj要绑定事件的对象
*eventStr事件的字符串
*func回调函数
*/
functionbind(obj,eventStr,func){
//判断是否有addEventListener()方法
if(obj.addEventListener){
//大部分浏览器兼容的方式
obj.addEventListener(eventStr,func,false);
}
else{
//IE8及以下注意on
//obj.attachEvent(\"on\"+eventStr,func);//此方法this为window下面提供解决方法
//统一this不直接调用func而是在匿名函数内调用
obj.attachEvent(\"on\"+eventStr,function(){
//在匿名函数内调用回调函数利用call()方法将this改为obj
func.call(obj);
});
}
};
</script>
</head>
<body>
<buttonid=\"btn1\">btn1</button>
</body>
</html>
Demand feedback