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