美文网首页
[Rust-async-book]--1--Getting St

[Rust-async-book]--1--Getting St

作者: buddyCoder | 来源:发表于2020-03-02 15:30 被阅读0次

原文传送门
Welcome to Asynchronous Programming in Rust! If you're looking to start writing asynchronous Rust code, you've come to the right place. Whether you're building a web server, a database, or an operating system, this book will show you how to use Rust's asynchronous programming tools to get the most out of your hardware.

欢迎使用Rust中的异步编程!如果您希望开始编写异步Rust代码,那么您已经找到了正确的地方。无论您正在构建web服务器、数据库还是操作系统,本书都将向您展示如何使用Rust的异步编程工具最大限度地利用硬件。

What This Book Covers

这本书的内容

This book aims to be a comprehensive, up-to-date guide to using Rust's async language features and libraries, appropriate for beginners and old hands alike.

The early chapters provide an introduction to async programming in general, and to Rust's particular take on it.

The middle chapters discuss key utilities and control-flow tools you can use when writing async code, and describe best-practices for structuring libraries and applications to maximize performance and reusability.
The last section of the book covers the broader async ecosystem, and provides a number of examples of how to accomplish common tasks.

With that out of the way, let's explore the exciting world of Asynchronous Programming in Rust!

这本书的目的是一个全面的,最新的指南使用Rust的异步语言功能和库,适合初学者和老手一样。
前几章一般介绍了异步编程,并特别介绍了Rust对异步编程的处理。
中间几章讨论了编写异步代码时可以使用的关键实用程序和控制流工具,并描述了构造库和应用程序以最大化性能和可重用性的最佳实践。
本书的最后一部分涵盖了更广泛的异步生态系统,并提供了一些如何完成常见任务的示例。
了解了这些之后,让我们来探索一下Rust中令人兴奋的异步编程世界!

Why Async?

We all love how Rust allows us to write fast, safe software. But why write asynchronous code?
Asynchronous code allows us to run multiple tasks concurrently on the same OS thread. In a typical threaded application, if you wanted to download two different webpages at the same time, you would spread the work across two different threads, like this:
我们都喜欢Rust让我们能够编写快速、安全的软件。但是为什么要编写异步代码呢?
异步代码允许我们在同一个OS线程上并发地运行多个任务。在一个典型的线程化应用程序中,如果你想同时下载两个不同的网页,你需要将工作分散到两个不同的线程中,如下所示:

fn get_two_sites() {
    // Spawn two threads to do work.
    let thread_one = thread::spawn(|| download("https://www.foo.com"));
    let thread_two = thread::spawn(|| download("https://www.bar.com"));

    // Wait for both threads to complete.
    thread_one.join().expect("thread one panicked");
    thread_two.join().expect("thread two panicked");
}

This works fine for many applications-- after all, threads were designed to do just this: run multiple different tasks at once. However, they also come with some limitations. There's a lot of overhead involved in the process of switching between different threads and sharing data between threads. Even a thread which just sits and does nothing uses up valuable system resources. These are the costs that asynchronous code is designed to eliminate. We can rewrite the function above using Rust's async/.await notation, which will allow us to run multiple tasks at once without creating multiple threads:

这对于许多应用程序来说都是可行的——毕竟,线程的设计目的就是这样的:一次运行多个不同的任务。然而,它们也有一些局限性。在不同线程之间切换和线程之间共享数据的过程涉及大量开销。即使一个线程只是坐着什么也不做,也会消耗宝贵的系统资源。异步代码就是为了消除这些成本而设计的。我们可以使用Rust的async/.await 上面的函数。它将允许我们运行多个任务一次而不创建多个线程:

async fn get_two_sites_async() {
    // Create two different "futures" which, when run to completion,
    // will asynchronously download the webpages.
    let future_one = download_async("https://www.foo.com");
    let future_two = download_async("https://www.bar.com");

    // Run both futures to completion at the same time.
    join!(future_one, future_two);
}

Overall, asynchronous applications have the potential to be much faster and use fewer resources than a corresponding threaded implementation. However, there is a cost. Threads are natively supported by the operating system, and using them doesn't require any special programming model-- any function can create a thread, and calling a function that uses threads is usually just as easy as calling any normal function. However, asynchronous functions require special support from the language or libraries. In Rust, async fn creates an asynchronous function which returns a Future. To execute the body of the function, the returned Future must be run to completion.

总的来说,异步应用程序可能比相应的线程实现要快得多,使用的资源也更少。然而,这是有代价的。线程由操作系统本身支持,使用线程不需要任何特殊的编程模型——任何函数都可以创建线程,调用使用线程的函数通常与调用任何普通函数一样简单。但是,异步函数需要语言或库的特殊支持。在Rust中,async fn创建一个返回Future的异步函数。要执行函数主体,必须运行返回的Future直到完成。

It's important to remember that traditional threaded applications can be quite effective, and that Rust's small memory footprint and predictability mean that you can get far without ever using async. The increased complexity of the asynchronous programming model isn't always worth it, and it's important to consider whether your application would be better served by using a simpler threaded model.

重要的是要记住,传统的线程应用程序可能非常有效,而且Rust占用的内存很小,并且具有可预测性,这意味着您可以在不使用异步的情况下取得很大的进展。异步编程模型增加的复杂性并不总是值得的,重要的是要考虑使用更简单的线程模型是否能更好地服务于应用程序。

The State of Asynchronous Rust

The asynchronous Rust ecosystem has undergone a lot of evolution over time, so it can be hard to know what tools to use, what libraries to invest in, or what documentation to read. However, the Future trait inside the standard library and the async/await language feature has recently been stabilized. The ecosystem as a whole is therefore in the midst of migrating to the newly-stabilized API, after which point churn will be significantly reduced.
随着时间的推移,异步Rust生态系统经历了很多演变,因此很难知道使用什么工具、投资什么库或阅读什么文档。然而,标准库中的Future trait 和 async/await 语言特性最近已经稳定下来。因此,整个生态系统正处于向新稳定的API迁移的过程中,在此之后,波动将显著减少。

At the moment, however, the ecosystem is still undergoing rapid development and the asynchronous Rust experience is unpolished. Most libraries still use the 0.1 definitions of the futures crate, meaning that to interoperate developers frequently need to reach for the compat functionality from the 0.3 futures crate. The async/await language feature is still new. Important extensions like async fn syntax in trait methods are still unimplemented, and the current compiler error messages can be difficult to parse.
但目前,该生态系统仍处于快速发展阶段,异步Rust的经验尚不完善。大多数库仍然使用futures crate的0.1定义,这意味着为了实现互操作,开发人员经常需要使用0.3 futures crate的compat功能。异步/等待语言特性仍然是新的。像trait方法中的async fn语法这样的重要扩展仍然没有实现,而且当前的编译器错误消息可能很难解析。
That said, Rust is well on its way to having some of the most performant and ergonomic support for asynchronous programming around, and if you're not afraid of doing some spelunking, enjoy your dive into the world of asynchronous programming in Rust!
也就是说,Rust正在为异步编程提供一些最高性能和最符合人体工程学的支持,如果您不害怕进行一些探索,那么就享受一下在Rust中深入异步编程的乐趣吧!

async/.await Primer

async/.await 是Rust的内置工具,用于编写类似于同步代码的异步函数。异步将代码块转换为实现call Future 特性的状态机。虽然在同步方法中调用阻塞函数会阻塞整个线程,但是阻塞的Future 将产生对线程的控制,从而允许其他 Future 运行。

To create an asynchronous function, you can use the async fn syntax:
要创建异步函数,可以使用async fn语法:

async fn do_something() { ... }

The value returned by async fn is a Future. For anything to happen, the Future needs to be run on an executor.
异步fn返回的值是一个将来值。任何事情要发生,未来都需要在执行者身上运行。

// `block_on` blocks the current thread until the provided future has run to
// completion. Other executors provide more complex behavior, like scheduling
// 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
}

Inside an async fn, you can use .await to wait for the completion of another type that implements the Future trait, such as the output of another async fn. Unlike block_on, .await doesn't block the current thread, but instead asynchronously waits for the future to complete, allowing other tasks to run if the future is currently unable to make progress.
在异步fn中,您可以使用.await来等待实现未来特征的另一个类型的完成,比如另一个异步fn的输出。与block_on不同,.await不会阻塞当前线程,而是异步地等待将来完成,允许在将来无法取得进展时运行其他任务。

For example, imagine that we have three async fn: learn_song, sing_song, and dance:
例如,假设我们有三个异步fn: learn_song、sing_song和dance:

async fn learn_song() -> Song { ... }
async fn sing_song(song: Song) { ... }
async fn dance() { ... }

One way to do learn, sing, and dance would be to block on each of these individually:
学习、唱歌和跳舞的一种方法是分别阻止以下每一种:

fn main() {
    let song = block_on(learn_song());
    block_on(sing_song(song));
    block_on(dance());
}

However, we're not giving the best performance possible this way-- we're only ever doing one thing at once! Clearly we have to learn the song before we can sing it, but it's possible to dance at the same time as learning and singing the song. To do this, we can create two separate async fn which can be run concurrently:
然而,我们并没有以这种方式提供最好的性能——我们总是一次只做一件事!很明显,在我们能唱这首歌之前,我们必须先学习这首歌,但是在学习和唱这首歌的同时也可以跳舞。为此,我们可以创建两个单独的异步fn可以并发运行:

async fn learn_and_sing() {
    // Wait until the song has been learned before singing it.
    // We use `.await` here rather than `block_on` to prevent blocking the
    // thread, which makes it possible to `dance` at the same time.
    let song = learn_song().await;
    sing_song(song).await;
}

async fn async_main() {
    let f1 = learn_and_sing();
    let f2 = dance();

    // `join!` is like `.await` but can wait for multiple futures concurrently.
    // If we're temporarily blocked in the `learn_and_sing` future, the `dance`
    // future will take over the current thread. If `dance` becomes blocked,
    // `learn_and_sing` can take back over. If both futures are blocked, then
    // `async_main` is blocked and will yield to the executor.
    futures::join!(f1, f2);
}

fn main() {
    block_on(async_main());
}

In this example, learning the song must happen before singing the song, but both learning and singing can happen at the same time as dancing. If we used block_on(learn_song()) rather than learn_song().await in learn_and_sing, the thread wouldn't be able to do anything else while learn_song was running. This would make it impossible to dance at the same time. By .await-ing the learn_song future, we allow other tasks to take over the current thread if learn_song is blocked. This makes it possible to run multiple futures to completion concurrently on the same thread.
在这个例子中,学习这首歌必须在唱这首歌之前,但是学习和唱歌可以和跳舞同时进行。如果我们使用block_on(learn_song())而不是learn_song()。在learn_and_sing中等待,当learn_song运行时,线程将不能做任何其他事情。这样就不可能同时跳舞了。在learn_song的未来中,如果learn_song被阻塞,我们允许其他任务接管当前线程。这使得在同一个线程上同时运行多个futures 操作成为可能。

Now that you've learned the basics of async/await, let's try out an example.
现在您已经了解了async/await的基础知识,让我们来尝试一个示例。

Applied: Simple HTTP Server

Let's use async/.await to build an echo server!
让我们使用 async/.await 建立一个回声服务器!
To start, run rustup update stable to make sure you've got stable Rust 1.39 or newer. Once you've done that, run cargo new async-await-echo to create a new project, and open up the resulting async-await-echo folder.
首先,运行rustup更新稳定,以确保你有稳定的锈1.39或更新。完成之后,运行cargo new async-await-echo来创建一个新项目,并打开生成的async-await-echo文件夹。

Let's add some dependencies to the Cargo.toml file:

[dependencies]
# Hyper is an asynchronous HTTP library. We'll use it to power our HTTP
# server and to make HTTP requests.
hyper = "0.13.0"
# To setup some sort of runtime needed by Hyper, we will use the Tokio runtime.
tokio = { version = "0.2", features = ["full"] }

# (only for testing)
failure = "0.1.6"
reqwest = "0.9.24"

Now that we've got our dependencies out of the way, let's start writing some code. We have some imports to add:
现在我们已经解决了依赖项的问题,让我们开始编写一些代码。我们有一些 imports 去添加:

use {
    hyper::{
        // Following functions are used by Hyper to handle a `Request`
        // and returning a `Response` in an asynchronous manner by using a Future
        service::{make_service_fn, service_fn},
        // Miscellaneous types from Hyper for working with HTTP.
        Body,
        Client,
        Request,
        Response,
        Server,
        Uri,
    },
    std::net::SocketAddr,
};

Once the imports are out of the way, we can start putting together the boilerplate to allow us to serve requests:
一旦导入完成,我们就可以开始整理样板文件,以便满足以下要求:

async fn serve_req(_req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
    // Always return successfully with a response containing a body with
    // a friendly greeting ;)
    Ok(Response::new(Body::from("hello, world!")))
}

async fn run_server(addr: SocketAddr) {
    println!("Listening on http://{}", addr);

    // Create a server bound on the provided address
    let serve_future = Server::bind(&addr)
        // Serve requests using our `async serve_req` function.
        // `serve` takes a closure which returns a type implementing the
        // `Service` trait. `service_fn` returns a value implementing the
        // `Service` trait, and accepts a closure which goes from request
        // to a future of the response.
        .serve(make_service_fn(|_| {
            async {
                {
                    Ok::<_, hyper::Error>(service_fn(serve_req))
                }
            }
        }));

    // Wait for the server to complete serving or exit with an error.
    // If an error occurred, print it to stderr.
    if let Err(e) = serve_future.await {
        eprintln!("server error: {}", e);
    }
}

#[tokio::main]
async fn main() {
  // Set the address to run our socket on.
  let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

  // Call our `run_server` function, which returns a future.
  // As with every `async fn`, for `run_server` to do anything,
  // the returned future needs to be run using `await`;
  run_server(addr).await;
}

If you cargo run now, you should see the message "Listening on http://127.0.0.1:3000" printed on your terminal. If you open that URL in your browser of choice, you'll see "hello, world!" appear in your browser. Congratulations! You just wrote your first asynchronous webserver in Rust.
如果您现在cargo run ,您应该在您的终端上看到打印的消息“Listening on http://127.0.0.1:3000”。如果在您选择的浏览器中打开该URL,您将看到“hello, world!”出现在您的浏览器中。恭喜你!您刚刚在Rust中编写了您的第一个异步web服务器。

You can also inspect the request itself, which contains information such as the request URI, HTTP version, headers, and other metadata. For example, we can print out the URI of the request like this:
您还可以检查请求本身,它包含请求URI、HTTP版本、标头和其他元数据等信息。例如,我们可以这样打印请求的URI:

fn main() {
println!("Got request at {:?}", req.uri());
}

You may have noticed that we're not yet doing anything asynchronous when handling the request-- we just respond immediately, so we're not taking advantage of the flexibility that async fn gives us. Rather than just returning a static message, let's try proxying the user's request to another website using Hyper's HTTP client.
您可能已经注意到,在处理请求时,我们还没有执行任何异步操作——我们只是立即响应,所以我们没有利用async fn提供给我们的灵活性。我们不只是返回一个静态消息,而是尝试使用Hyper的HTTP客户机将用户的请求代理到另一个网站。

We start by parsing out the URL we want to request:
我们首先解析出我们想要请求的URL:

let url_str = "http://www.rust-lang.org/en-US/";
let url = url_str.parse::<Uri>().expect("failed to parse URL");

Then we can create a new hyper::Client and use it to make a GET request, returning the response to the user:
然后我们可以创建一个新的hyper::Client并使用它发出一个GET请求,将响应返回给用户:

        let res = Client::new().get(url).await?;
        // Return the result of the request directly to the user
        println!("request finished-- returning response");
        Ok(res)

Client::get returns a hyper::client::ResponseFuture, which implements Future<Output = Result<Response<Body>>> (or Future<Item = Response<Body>, Error = Error> in futures 0.1 terms). When we .await that future, an HTTP request is sent out, the current task is suspended, and the task is queued to be continued once a response has become available.

Client::get返回一个hyper:: Client:: ResponseFuture,它实现了Future>>(或者Future, Error = Error> in futures 0.1 terms)。当我们等待那个未来时,将发送一个HTTP请求,当前任务将被挂起,并且一旦响应可用,任务将排队继续执行。

Now, if you cargo run and open http://127.0.0.1:3000/foo in your browser, you'll see the Rust homepage, and the following terminal output:
现在,如果您在浏览器中运行并打开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

Congratulations! You just proxied an HTTP request.

相关文章

网友评论

      本文标题:[Rust-async-book]--1--Getting St

      本文链接:https://www.haomeiwen.com/subject/echvkhtx.html