Tokio 实验性 async / await 支持
2018年8月27日
星期一快乐!
如果您还没听说,async
/ await
是 Rust 正在开发的一个重要新特性。它的目标是使异步编程变得容易(好吧,至少比今天稍微容易一点)。这项工作已经进行了一段时间,并且今天已经在 Rust nightly 频道上可用。
我很高兴地宣布 Tokio 现在有了实验性的 async / await 支持!让我们深入了解一下。
开始入门
首先,Tokio async/await 支持由一个新的 crate 提供,这个 crate 的名字很有创意,叫做 tokio-async-await
。这个 crate 是 tokio
的一个垫片层。它包含与 tokio
相同的所有类型和函数(作为重新导出),以及用于 async
/ await
的其他辅助工具。
要使用 tokio-async-await
,您需要从配置为使用 Rust 2018 edition 的 crate 中依赖它。它也只适用于最近的 Rust nightly 版本。
在您的应用程序的 Cargo.toml
中,添加以下内容
# At the very top of the file
cargo-features = ["edition"]
# In the `[packages]` section
edition = "2018"
# In the `[dependencies]` section
tokio = {version = "0.1", features = ["async-await-preview"]}
然后,在您的应用程序中,执行以下操作
// The nightly features that are commonly needed with async / await
#![feature(await_macro, async_await, futures_api)]
// This pulls in the `tokio-async-await` crate. While Rust 2018
// doesn't require `extern crate`, we need to pull in the macros.
#[macro_use]
extern crate tokio;
fn main() {
// And we are async...
tokio::run_async(async {
println!("Hello");
});
}
并运行它(使用 nightly 版本)
cargo +nightly run
您就正在使用 Tokio + async
/ await
了!
请注意,要 spawn async
代码块,应使用 tokio::run_async
函数(而不是 tokio::run
)。
深入了解
现在,让我们构建一些简单的东西:一个 echo 服务器 (耶!)。
// Somewhere towards the top
#[macro_use]
extern crate tokio;
use tokio::net::{TcpListener, TcpStream};
use tokio::prelude::*;
// more to come...
// The main function
fn main() {
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let listener = TcpListener::bind(&addr).unwrap();
tokio::run_async(async {
let mut incoming = listener.incoming();
while let Some(stream) = await!(incoming.next()) {
let stream = stream.unwrap();
handle(stream);
}
});
}
在这个例子中,incoming
是已接受的 TcpStream
值的 stream。我们正在使用 async
/ await
来迭代 stream。目前,只有等待单个值(future)的语法,所以我们使用 next
组合器来获取 stream 中下一个值的 future。这使我们可以使用 while
语法迭代 stream。
一旦我们获得 stream,它将被传递给 handle
函数进行处理。让我们看看它是如何实现的。
fn handle(mut stream: TcpStream) {
tokio::spawn_async(async move {
let mut buf = [0; 1024];
loop {
match await!(stream.read_async(&mut buf)).unwrap() {
0 => break, // Socket closed
n => {
// Send the data back
await!(stream.write_all_async(&buf[0..n])).unwrap();
}
}
}
});
}
就像 run_async
一样,有一个 spawn_async
函数可以将 async 代码块 spawn 为 tasks。
然后,为了执行 echo 逻辑,我们从 socket 读取数据到 buffer 中,并将数据写回同一个 socket。因为我们正在使用 async
/ await
,我们可以使用一个看起来是栈分配的数组(它实际上最终会在堆上)。
请注意,TcpStream
具有 read_async
和 write_all_async
函数。这些函数执行与 std
中 Read
和 Write
traits 上存在的同步等效函数相同的逻辑。不同之处在于,它们返回可以被 await 的 futures。
*_async
函数在 tokio-async-await
crate 中通过使用 extension traits 定义。这些 traits 通过 use tokio::prelude::*;
行导入。
这只是一个开始,请查看仓库中的 examples 目录以获取更多信息。甚至有一个使用了 hyper 的示例。
一些注意事项
首先,tokio-async-await
crate 仅提供对 async
/ await
语法的兼容性。它不提供对 futures
0.3 crate 的支持。预计用户将继续使用 futures 0.1 以保持与 Tokio 的兼容性。
为了使这项工作正常进行,tokio-async-await
crate 定义了自己的 await!
macro。这个 macro 是 std
提供的 macro 的一个垫片层,它能够等待 futures
0.1 futures。这就是兼容层能够保持轻量级和无样板代码的原因。
这只是一个开始。async
/ await
支持将随着时间的推移继续发展和改进,但这足以让每个人开始使用!
就这样,祝您一周愉快!