【问题标题】:How do I synchronously return a value calculated in an asynchronous Future in stable Rust?如何在稳定的 Rust 中同步返回在异步 Future 中计算的值?
【发布时间】:2019-03-02 10:28:53
【问题描述】:

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

【问题讨论】:

    标签: rust future hyper


    【解决方案1】:

    标准库期货

    让我们用它作为我们的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);
    }
    

    东京

    在任何函数上使用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

    异步标准

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

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

    此方法不适合在事件循环或类似 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 保证将未来运行到完成。

    另见:

    【讨论】:

    • 在 futures 0.1 中,对于简单的情况,你只需要调用 wait:在我看来不是很清楚:它只有在 future 立即解决时才有效。当您将来有要解决的问题时,您需要一个运行时,对于简单的情况也是如此。
    • @Shepmaster,从 Rust 1.45 开始,有没有标准的方法(不使用其他 crates)来做这件事?
    • @Shepmaster,谢谢。我认为这是障碍之一,希望有一天我们能做到吗?
    • @Amani 如果您认为在 Cargo.toml 中添加一行是“路障”,那么 Rust 可能不是您喜欢使用的语言。总是有可能将更多的东西移到标准库中,但实际上并没有强烈的推动力,尤其是当有多种方法可以解决一个问题,而其中一个与另一个没有明显的好处时 and 添加依赖很容易。
    【解决方案2】:

    这适用于我使用 tokio:

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

    【讨论】:

      【解决方案3】:

      由于这是“如何在 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() 的所有警告。

      【讨论】:

        猜你喜欢
        • 2019-10-08
        • 1970-01-01
        • 2013-06-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-29
        • 1970-01-01
        • 2017-09-24
        相关资源
        最近更新 更多