ハイパーを使用してHTMLページのコンテンツを取得しようとしています。futureの出力を同期的に返したいのですが。同期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()
を呼び出す前にタスクを適切に起動できなかったことを理解していますが、それが可能であるとしても、適切に実行する方法を見つけることができませんでした。
これを 最小限の再現可能な例 として使用しましょう:
async fn example() -> i32 {
42
}
呼び出し executor::block_on
:
use futures::executor; // 0.3.1
fn main() {
let v = executor::block_on(example());
println!("{}", v);
}
非同期関数から同期関数に変換するには、(main
!だけでなく)関数の tokio::main
属性を使用します。 1:
use tokio; // 0.2.4
async fn main() {
let v = example().await
println!("{}", v);
}
tokio::main
はこれを変換するマクロです
#[tokio::main]
async fn main() {}
これに:
fn main() {
tokio::runtime::Builder::new()
.basic_scheduler()
.threaded_scheduler()
.enable_all()
.build()
.unwrap()
.block_on(async { {} })
}
これは内部で Runtime::block_on
を使用するため、次のように記述することもできます。
use tokio::runtime::Runtime; // 0.2.6
fn main() {
let v = Runtime::new().unwrap().block_on(example());
println!("{}", v);
}
非同期関数から同期関数に変換するには、main
関数の async_std::main
属性を使用します。
use async_std; // 1.5.0, features = ["attributes"]
#[async_std::main]
async fn main() {
let v = example().await;
println!("{}", v);
}
これを 最小限の再現可能な例 として使用しましょう:
use futures::{future, Future}; // 0.1.27
fn example() -> impl Future<Item = i32, Error = ()> {
future::ok(42)
}
単純なケースでは、wait
を呼び出すだけです。
fn main() {
let s = example().wait();
println!("{:?}", s);
}
ただし、これにはかなり厳しい警告が伴います。
このメソッドは、イベントループの進行を妨げる(これによりスレッドがブロックされる)ため、イベントループや同様のI/O状況での呼び出しには適していません。このメソッドは、このフューチャーに関連するブロッキング作業が別のスレッドによって完了することが保証されている場合にのみ呼び出す必要があります。
Tokio 0.1を使用している場合は、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);
}
block_on
の実装をのぞくと、実際に未来の結果がチャネルに送信され、そのチャネルでwait
が呼び出されます。 Tokioは未来を完全に実行することを保証しているので、これは問題ありません。
以下も参照してください。