如何在稳定的 Rust 中同步 return 在异步 Future 中计算的值?

How do I synchronously return a value calculated in an asynchronous Future in stable Rust?

我正在尝试使用 hyper 获取 HTML 页面的内容,并希望同步 return 未来的输出。我意识到我可以选择一个更好的例子,因为同步 HTTP 请求已经存在,但我更感兴趣的是了解我们是否可以 return 来自异步计算的值。

extern crate futures;
extern crate hyper;
extern crate hyper_tls;
extern crate tokio;

use futures::{future, Future, Stream};
use hyper::Client;
use hyper::Uri;
use hyper_tls::HttpsConnector;

use std::str;

fn scrap() -> Result<String, String> {
    let scraped_content = future::lazy(|| {
        let https = HttpsConnector::new(4).unwrap();
        let client = Client::builder().build::<_, hyper::Body>(https);

        client
            .get("https://hyper.rs".parse::<Uri>().unwrap())
            .and_then(|res| {
                res.into_body().concat2().and_then(|body| {
                    let s_body: String = str::from_utf8(&body).unwrap().to_string();
                    futures::future::ok(s_body)
                })
            }).map_err(|err| format!("Error scraping web page: {:?}", &err))
    });

    scraped_content.wait()
}

fn read() {
    let scraped_content = future::lazy(|| {
        let https = HttpsConnector::new(4).unwrap();
        let client = Client::builder().build::<_, hyper::Body>(https);

        client
            .get("https://hyper.rs".parse::<Uri>().unwrap())
            .and_then(|res| {
                res.into_body().concat2().and_then(|body| {
                    let s_body: String = str::from_utf8(&body).unwrap().to_string();
                    println!("Reading body: {}", s_body);
                    Ok(())
                })
            }).map_err(|err| {
                println!("Error reading webpage: {:?}", &err);
            })
    });

    tokio::run(scraped_content);
}

fn main() {
    read();
    let content = scrap();

    println!("Content = {:?}", &content);
}

该示例编译并且对 read() 的调用成功,但对 scrap() 的调用出现恐慌并显示以下错误消息:

Content = Err("Error scraping web page: Error { kind: Execute, cause: None }")

我知道我在将来调用 .wait() 之前未能正确启动任务,但我找不到正确的方法,假设它甚至可能。

标准库期货

让我们用这个作为我们的 minimal, reproducible example:

async fn example() -> i32 {
    42
}

呼叫executor::block_on:

use futures::executor; // 0.3.1

fn main() {
    let v = executor::block_on(example());
    println!("{}", v);
}

东京

在任何函数(不仅仅是 main!)上使用 tokio::main 属性将其从异步函数转换为同步函数:

use tokio; // 0.3.5

#[tokio::main]
async fn main() {
    let v = example().await;
    println!("{}", v);
}

tokio::main是一个宏,把这个

#[tokio::main]
async fn main() {}

进入这个:

fn main() {
    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap()
        .block_on(async { {} })
}

这在幕后使用了 Runtime::block_on,因此您也可以将其写为:

use tokio::runtime::Runtime; // 0.3.5

fn main() {
    let v = Runtime::new().unwrap().block_on(example());
    println!("{}", v);
}

对于测试,您可以使用 tokio::test

异步标准

使用 main 函数的 async_std::main 属性将其从异步函数转换为同步函数:

use async_std; // 1.6.5, features = ["attributes"]

#[async_std::main]
async fn main() {
    let v = example().await;
    println!("{}", v);
}

对于测试,您可以使用 async_std::test

期货 0.1

让我们用这个作为我们的 minimal, reproducible example:

use futures::{future, Future}; // 0.1.27

fn example() -> impl Future<Item = i32, Error = ()> {
    future::ok(42)
}

对于简单的情况,只需要调用wait:

fn main() {
    let s = example().wait();
    println!("{:?}", s);
}

但是,这带有一个非常严重的警告:

This method is not appropriate to call on event loops or similar I/O situations because it will prevent the event loop from making progress (this blocks the thread). This method should only be called when it's guaranteed that the blocking work associated with this future will be completed by another thread.

东京

如果你使用的是 Tokio 0.1,你应该使用 Tokio 的 Runtime::block_on:

use tokio; // 0.1.21

fn main() {
    let mut runtime = tokio::runtime::Runtime::new().expect("Unable to create a runtime");
    let s = runtime.block_on(example());
    println!("{:?}", s);
}

如果您查看 block_on 的实现,它实际上将 future 的结果发送到一个通道,然后在该通道上调用 wait!这很好,因为 Tokio 保证 运行 未来完成。

另请参阅:

这对我使用 tokio 有用:

tokio::runtime::Runtime::new()?.block_on(fooAsyncFunction())?;

由于这是查询“如何在 Rust 中从同步调用异步”在搜索引擎中出现的最高结果,我决定在这里分享我的解决方案。我觉得可能有用。

正如@Shepmaster 提到的,回到 0.1 版 futures crate 有漂亮的方法 .wait() 可以用来从同步函数调用异步函数。然而,这个必备方法已从 crate 的更高版本中删除。

幸运的是,重新实现它并不难:

trait Block {
    fn wait(self) -> <Self as futures::Future>::Output
        where Self: Sized, Self: futures::Future
    {
        futures::executor::block_on(self)
    }
}

impl<F,T> Block for F
    where F: futures::Future<Output = T>
{}

之后,您只需执行以下操作:

async fn example() -> i32 {
    42
}

fn main() {
    let s = example().wait();
    println!("{:?}", s);
}

请注意,@Shepmaster 的回答中解释了原始 .wait() 的所有警告。