web-dev-qa-db-ja.com

安定したRustで非同期Futureで計算された値を同期的に返すにはどうすればよいですか?

ハイパーを使用して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()を呼び出す前にタスクを適切に起動できなかったことを理解していますが、それが可能であるとしても、適切に実行する方法を見つけることができませんでした。

12
Boris

標準ライブラリの先物

これを 最小限の再現可能な例 として使用しましょう:

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

async-std

非同期関数から同期関数に変換するには、main関数の async_std::main 属性を使用します。

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

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

先物0.1

これを 最小限の再現可能な例 として使用しましょう:

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は未来を完全に実行することを保証しているので、これは問題ありません。

以下も参照してください。

18
Shepmaster