Hello Tokio

未匹配的标注

原文链接:tokio.rs/tokio/tutorial

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 函数主要用于启动程序不同:

  1. 它需要是一个 async fn
  2. 它需要添加 #[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" 功能特性标记。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/tokio-doc/hello...

译文地址:https://learnku.com/docs/tokio-doc/hello...

上一篇 下一篇
贡献者:3
讨论数量: 0
发起讨论 只看当前版本


暂无话题~