¿Cómo devuelvo sincrónicamente un valor calculado en un futuro asincrónico?
Estoy intentando utilizar Hyper para capturar el contenido de una página HTML y me gustaría devolver sincrónicamente el resultado de un futuro. Me di cuenta de que podría haber elegido un ejemplo mejor ya que ya existen solicitudes HTTP sincrónicas, pero estoy más interesado en comprender si podríamos devolver un valor a partir de un cálculo asíncrono.
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);
}
El ejemplo se compila y la llamada a read()
se realiza correctamente, pero la llamada a entra en scrap()
pánico con el siguiente mensaje de error:
Content = Err("Error scraping web page: Error { kind: Execute, cause: None }")
Entiendo que no pude iniciar la tarea correctamente antes de invocar .wait()
el futuro, pero no pude encontrar cómo hacerlo correctamente, suponiendo que sea posible.
Futuros de biblioteca estándar
Usemos esto como nuestro ejemplo mínimo y reproducible :
async fn example() -> i32 {
42
}
Llamar executor::block_on
:
use futures::executor; // 0.3.1
fn main() {
let v = executor::block_on(example());
println!("{}", v);
}
Tokio
Utilice el tokio::main
atributo en cualquier función (¡no solo main
!) para convertirla de una función asincrónica a una sincrónica:
use tokio; // 0.3.5
#[tokio::main]
async fn main() {
let v = example().await;
println!("{}", v);
}
tokio::main
es una macro que transforma esto
#[tokio::main]
async fn main() {}
Dentro de esto:
fn main() {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async { {} })
}
Esto se usa Runtime::block_on
debajo del capó, por lo que también puedes escribir esto como:
use tokio::runtime::Runtime; // 0.3.5
fn main() {
let v = Runtime::new().unwrap().block_on(example());
println!("{}", v);
}
Para las pruebas, puede utilizartokio::test
.
estándar asíncrono
Utilice el async_std::main
atributo de la main
función para convertirla de una función asíncrona a una síncrona:
use async_std; // 1.6.5, features = ["attributes"]
#[async_std::main]
async fn main() {
let v = example().await;
println!("{}", v);
}
Para las pruebas, puede utilizarasync_std::test
.
Futuros 0,1
Usemos esto como nuestro ejemplo mínimo y reproducible :
use futures::{future, Future}; // 0.1.27
fn example() -> impl Future<Item = i32, Error = ()> {
future::ok(42)
}
Para casos sencillos, sólo es necesario llamar a wait
:
fn main() {
let s = example().wait();
println!("{:?}", s);
}
Sin embargo, esto viene acompañado de una advertencia bastante severa:
Este método no es apropiado para invocar bucles de eventos o situaciones de E/S similares porque evitará que el bucle de eventos avance (esto bloquea el hilo). Este método solo debe invocarse cuando se garantiza que otro subproceso completará el trabajo de bloqueo asociado con este futuro.
Tokio
Si estás usando Tokio 0.1, deberías usar 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);
}
Si echa un vistazo a la implementación de block_on
, en realidad envía el resultado del futuro por un canal y luego llama wait
a ese canal. Esto está bien porque Tokio garantiza ejecutar el futuro hasta su finalización.
Ver también:
- ¿Cómo puedo extraer eficientemente el primer elemento de un futuro::Stream de forma bloqueante?
Esto me funciona usando tokio:
tokio::runtime::Runtime::new()?.block_on(fooAsyncFunction())?;
Como este es el resultado principal que aparece en los motores de búsqueda con la consulta "Cómo llamar a async desde sincronización en Rust", decidí compartir mi solución aquí. Creo que podría ser útil.
Como mencionó @Shepmaster, en la versión 0.1, futures
la caja tenía un método hermoso .wait()
que podía usarse para llamar a una función asíncrona desde una de sincronización. Sin embargo, este método imprescindible se eliminó de versiones posteriores de la caja.
Por suerte, no es tan difícil volver a implementarlo:
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>
{}
Después de eso, puedes hacer lo siguiente:
async fn example() -> i32 {
42
}
fn main() {
let s = example().wait();
println!("{:?}", s);
}
Tenga en cuenta que esto viene con todas las advertencias del original .wait()
explicadas en la respuesta de @Shepmaster.