首页 > 解决方案 > 如何在稳定的 Rust 中同步返回在异步 Future 中计算的值?

问题描述

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

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()未来之前我未能正确启动任务,但我找不到如何正确执行它,假设它甚至是可能的。

标签: rustfuturehyper

解决方案


标准库期货

让我们用它作为我们最小的、可重现的例子

async fn example() -> i32 {
    42
}

致电executor::block_on

use futures::executor; // 0.3.1

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

东京

使用tokio::main任何函数的属性(不仅仅是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.

异步标准

使用函数async_std::main上的属性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

让我们用它作为我们最小的、可重现的例子

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);
}

然而,这伴随着一个非常严重的警告:

此方法不适合在事件循环或类似 I/O 情况下调用,因为它会阻止事件循环继续进行(这会阻塞线程)。只有在保证与此未来相关的阻塞工作将由另一个线程完成时,才应调用此方法。

东京

如果您使用的是 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,它实际上会将未来的结果发送到一个通道,然后调用wait该通道!这很好,因为 Tokio 保证将未来运行到完成。

也可以看看:


推荐阅读