web-dev-qa-db-fra.com

Comment renvoyer de manière synchrone une valeur calculée dans un avenir asynchrone dans Rust stable?

J'essaie d'utiliser hyper pour saisir le contenu d'une page HTML et je voudrais retourner de manière synchrone la sortie d'un futur. J'ai réalisé que j'aurais pu choisir un meilleur exemple car des requêtes HTTP synchrones existent déjà, mais je suis plus intéressé à comprendre si nous pouvons retourner une valeur à partir d'un calcul asynchrone.

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

L'exemple se compile et l'appel à read() réussit, mais l'appel à scrap() panique avec le message d'erreur suivant:

Content = Err("Error scraping web page: Error { kind: Execute, cause: None }")

Je comprends que je n'ai pas réussi à lancer la tâche correctement avant d'appeler .wait() à l'avenir, mais je n'ai pas trouvé comment le faire correctement, en supposant que c'est même possible.

12
Boris

Futurs de bibliothèque standard

Utilisons cela comme notre exemple minimal et reproductible :

async fn example() -> i32 {
    42
}

Appelez executor::block_on :

use futures::executor; // 0.3.1

fn main() {
    let v = executor::block_on(example());
    println!("{}", v);
}

Tokio

Utilisez l'attribut tokio::main sur n'importe quelle fonction (et pas seulement main!) Pour la convertir d'une fonction asynchrone en fonction synchrone:

use tokio; // 0.2.4

async fn main() {
    let v = example().await
    println!("{}", v);
}

tokio::main Est une macro qui transforme cela

#[tokio::main]
async fn main() {}

En cela:

fn main() {
    tokio::runtime::Builder::new()
        .basic_scheduler()
        .threaded_scheduler()
        .enable_all()
        .build()
        .unwrap()
        .block_on(async { {} })
}

Cela utilise Runtime::block_on sous le capot, vous pouvez donc également écrire ceci comme:

use tokio::runtime::Runtime; // 0.2.6

fn main() {
    let v = Runtime::new().unwrap().block_on(example());
    println!("{}", v);
}

async-std

Utilisez l'attribut async_std::main de la fonction main pour la convertir d'une fonction asynchrone en fonction synchrone:

use async_std; // 1.5.0, features = ["attributes"]

#[async_std::main]
async fn main() {
    let v = example().await;
    println!("{}", v);
}

Futures 0,1

Utilisons cela comme notre exemple minimal et reproductible :

use futures::{future, Future}; // 0.1.27

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

Pour les cas simples, il vous suffit d'appeler wait:

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

Cependant, cela vient avec un avertissement assez sévère:

Cette méthode n'est pas appropriée pour appeler des boucles d'événements ou des situations d'E/S similaires car elle empêchera la boucle d'événements d'avancer (cela bloque le thread). Cette méthode ne doit être appelée que lorsqu'il est garanti que le travail de blocage associé à ce futur sera terminé par un autre thread.

Tokio

Si vous utilisez Tokio 0.1, vous devez utiliser 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 vous jetez un œil à l'implémentation de block_on, Il envoie en fait le résultat futur sur un canal, puis appelle wait sur ce canal! C'est très bien parce que Tokio garantit d'exécuter le futur jusqu'à son terme.

Voir également:

18
Shepmaster