¿Cómo devuelvo sincrónicamente un valor calculado en un futuro asincrónico?

Resuelto Boris asked hace 5 años • 3 respuestas

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.

Boris avatar Sep 26 '18 22:09 Boris
Aceptado

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::mainatributo 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::maines 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_ondebajo 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::mainatributo de la mainfunció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 waita 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?
Shepmaster avatar Sep 26 '2018 15:09 Shepmaster

Esto me funciona usando tokio:

tokio::runtime::Runtime::new()?.block_on(fooAsyncFunction())?;
Chris avatar Feb 23 '2021 08:02 Chris

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, futuresla 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.

Guestie avatar Oct 21 '2021 19:10 Guestie