Как мне синхронно вернуть значение, рассчитанное в асинхронном будущем в стабильном 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
К счастью, это не так уж и сложно реализовать заново:
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);
}
Помните, что это идет со всеми оговорками оригинального
Это работает для меня с использованием tokio:
tokio::runtime::Runtime::new()?.block_on(fooAsyncFunction())?;