Как мне синхронно вернуть значение, рассчитанное в асинхронном будущем в стабильном Rust?

Я пытаюсь использовать гипер для захвата содержимого 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() на будущее, но я не мог найти, как правильно сделать это, предполагая, что это даже возможно.

3 ответа

Решение

Давайте использовать это как наш MCVE:

extern crate futures; // 0.1.24

use futures::{future, Future};

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

В фьючерсах 0.1, для простых случаев вам нужно всего лишь позвонить wait:

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

Однако это сопровождается довольно серьезным предупреждением:

Этот метод не подходит для вызова циклов обработки событий или аналогичных ситуаций ввода-вывода, поскольку он будет препятствовать выполнению цикла обработки событий (это блокирует поток). Этот метод следует вызывать только тогда, когда гарантируется, что работа по блокировке, связанная с этим будущим, будет завершена другим потоком.

Если вы используете Токио, вы должны использовать Токио Runtime::block_on:

extern crate tokio; // 0.1.8

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 на этом канале! Это хорошо, потому что Токио гарантирует, что будущее будет завершено.

Так как это лучший результат, который появляется в поисковых системах по запросу «Как вызвать асинхронную синхронизацию из синхронизации в Rust», я решил поделиться своим решением здесь. Думаю, это может быть полезно.

Как упоминал @Shepmaster, еще в версии 0.1 В 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.

Это работает для меня с использованием tokio:

      tokio::runtime::Runtime::new()?.block_on(fooAsyncFunction())?;
Другие вопросы по тегам