Hello Tokio
Hello Tokio
我们将从编写一个非常简单的Tokio应用程序开始。它会连接到Mini-Redis服务器,并将键“hello”的值设置为“world”,然后读取值。这将使用Mini-Redis客户端库完成。
代码
生成一个新的crate
我们从生成一个新的Rust应用程序开始:
$ cargo new my-redis
$ cd my-redis
添加依赖
接着,打开 Cargo.toml
文件并在 [dependencies]
添加下列依赖:
tokio = { version = "1", features = ["full"] }
mini-redis = "0.4"
编写代码
然后,打开 main.rs
并将文件内容替换为下列代码:
use mini_redis::{client, Result};
#[tokio::main]
async fn main() -> Result<()> {
// 打开一个mini-redis连接.
let mut client = client::connect("127.0.0.1:6379").await?;
// 将名为"hello"的key的值设置为"world"
client.set("hello", "world".into()).await?;
// 获取名为"hello"的key的值
let result = client.get("hello").await?;
println!("got value from the server; result={:?}", result);
Ok(())
}
先确保 Mini-Redis 服务端正在运行。 可以在单独的终端窗口中运行以下命令:
$ mini-redis-server
如果您还没有安装 mini-redis,可以使用以下命令安装:
$ cargo install mini-redis
现在,让我们运行 my-redis
程序:
$ cargo run
got value from the server; result=Some(b"world")
顺利运行!
您可以在 这里 获取完整代码.
分析一下
让我们花些时间回顾一下刚刚做了什么。 代码虽然不多,但其实做了很多事。
let mut client = client::connect("127.0.0.1:6379").await?;
client::connect
函数是由 mini-redis
库提供的。 它与指定的远程地址异步建立 TCP 连接。 建立连接后,将返回 client
。 尽管操作是异步执行的,但我们编写的代码看起来就像是同步的。 该操作是异步的唯一标识符就是.await
操作。
什么是异步编程?
大多数计算机程序的执行顺序与它们的编写顺序相同。第一行执行,然后是下一行,依此类推。使用同步编程,当程序遇到无法立即完成的操作时,它会阻塞,直到操作完成。例如,建立TCP 连接需要通过网络与对端进行数据交换,这可能需要相当长的时间。在此期间,线程被阻塞。
如果使用异步编程,无法立即完成的操作将会在后台挂起。线程没有阻塞,可以继续运行其他东西。一旦操作完成,任务就会取消挂起,并从中断的地方继续运行。我们之前的示例只有一个任务,所以挂起时什么也没发生,但是异步程序通常有很多这样的任务。
尽管异步编程可以带来更快的运行速度,但通常也会导致程序更加复杂。一旦异步操作完成,程序员需要跟踪恢复工作所需的所有状态。从过往来看,这是一项乏味且容易出错的任务。
Compile-time green-threading
Rust使用名为 async/await
的特性实现异步编程。 执行异步操作的函数会标有 async
关键字。在我们的示例中,connect
函数的定义如下:
use mini_redis::Result;
use mini_redis::client::Client;
use tokio::net::ToSocketAddrs;
pub async fn connect<T: ToSocketAddrs>(addr: T) -> Result<Client> {
// ...
}
async fn
定义看起来像一个常规的同步函数一样,但实际操作是异步的。Rust在编译期会将 async fn
转换为异步操作。在 async fn
中对 . wait
的任何调用都会将控制权交还给线程。当操作在后台处理时,线程可能会做其他工作。
尽管其他语言也实现了
async/await
,Rust 采用了一种独特的方式。 首先, Rust 的异步操作是 惰性的。 这也导致了运行时与其他语言有所区别。
如果您还觉得没有太多感觉,不要着急。 在指南中我们还会探索更多 async/await
的相关内容。
使用 async/await
异步函数的调用与其他任何Rust函数一样。但是,调用这些函数不会导致函数体立即执行。相反,调用async fn
会返回一个表示操作的值。这在概念上类似于零参数闭包。要实际运行操作,您应该在返回值上使用 . wait
运算符。
举例来说,就像下面的程序:
async fn say_world() {
println!("world");
}
#[tokio::main]
async fn main() {
// 调用 `say_world()` 函数,但是不会执行 `say_world()` 函数体。
let op = say_world();
// 这里会首先执行 println! 输出
println!("hello");
// 在`op`上面调用`.await` 才开始执行 `say_world`。
op.await;
}
输出结果如下:
hello
world
async fn
的返回值是一个实现了 Future
trait的匿名类型。
异步 main
函数
与大多数 Rust 的 creats 中的main 函数主要用于启动程序不同:
- 它需要是一个
async fn
- 它需要添加
#[tokio::main]
注解
async fn
用于我们想要的异步上下文。但是,异步函数必须由 运行时执行。 运行时包含异步任务调度器,提供事件I/O、定时器等。运行时不会自动启动,所以main函数需要启动它。
#[tokio::main]
函数是一个宏。它会将 async fn main()
转换为同步 fn main()
来初始化运行时实例并执行async main函数。
例如,下面这样的代码:
#[tokio::main]
async fn main() {
println!("hello");
}
它会转换为:
fn main() {
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
println!("hello");
})
}
稍后将介绍Tokio运行时的详细信息。
Cargo features
本教程在依赖 tokio 时, 会使用 full
功能特性标记:
tokio = { version = "1", features = ["full"] }
Tokio有很多功能 (TCP, UDP, Unix套接字, timers, sync utilities, 多种调度类型等等)。 并不是所有应用程序都需要所有功能。当尝试优化编译时间或最终应用程序大小时,应用程序可以决定选择仅它使用的功能。
但是现在,我们在依赖 tokio 的时候仍然会使用 "full" 功能特性标记。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: