Rust 异步
作者:@AlienKevin
This library does not yet support returning a Future type from Rust and this has to do with the difficulty of uniting the various approaches to async in Rust. The Rust Book summarized the current state of async support succinctly:
The most fundamental traits, types and functions, such as the Future trait are provided by the standard library. The async/await syntax is supported directly by the Rust compiler.
Many utility types, macros and functions are provided by the futures crate. They can be used in any async Rust application.
Execution of async code, IO and task spawning are provided by "async runtimes", such as Tokio and async-std. Most async applications, and some async crates, depend on a specific runtime.
While the futures crate provides an executor called
futures::executor::block_on
, libraries that use Tokio runtime cannot use this
executor. According to
Rust-lang community wiki,
crates like Tokio that provide both a runtime and IO abstractions often have
their IO depend on the runtime. This can make it difficult to write
runtime-agnostic code. First, we demonstrate a common use case of async
programming in Rust by attempting to fetch the content of a file from the
internet using the popular HTTP Client
Reqwest:
use anyhow;
async fn get() -> anyhow::Result<String> {
let url = "https://link/to/file/download";
let data = reqwest::get(url).await?.text().await?;
Ok(data)
}
When you try to generate bindings for the get
function, the generated code
will contain errors because this library does not support returning Future from
Rust.
Mismatched runtime
The next logic thing to try would be to convert the asynchronous code to
synchronous by directly blocking the current thread and execute the code. For
our first attempt, we wrap futures::executor::block_on
around an async block
containing reqwest calls.
use anyhow;
use futures::executor::block_on;
fn get() -> anyhow::Result<String> {
block_on(async {
let url = "https://link/to/file/download";
let data = reqwest::get(url).await?.text().await?;
Ok(data)
})
}
Since Reqwest uses the Tokio runtime instead of the futures runtime, our code
panicked with the error "there is no reactor running, must be called from the
context of a Tokio 1.x runtime". To fix this error, we have two ways to execute
async codes using the Tokio runtime. Approach 1 is the simplest and uses the
convenient tokio::main
macro to turn an async function to a synchronous one. Approach 2 requires you to
explicitly create a new Tokio runtime and use its block_on function to run the
future to completion.
Approach 1 (macro)
use anyhow;
#[tokio::main(flavor = "current_thread")]
async fn get() -> anyhow::Result<String> {
let url = "https://link/to/file/download";
let data = reqwest::get(url).await?.text().await?;
Ok(data)
}
It has the following dependencies:
[dependencies]
futures = "0.3"
reqwest = "0.11.6"
tokio = { version = "1.14.0", features = ["rt", "macros"] }
anyhow = { version = "1.0.49" }
Approach 2 (runtime)
use anyhow;
use tokio::runtime::Runtime;
fn get() -> anyhow::Result<String> {
let rt = Runtime::new().unwrap();
rt.block_on(async {
let url = "https://link/to/file/download";
let data = reqwest::get(url).await?.text().await?;
Ok(data)
})
}
It has the following dependencies:
[dependencies]
futures = "0.3"
reqwest = "0.11.6"
tokio = { version = "1.14.0", features = ["rt-multi-thread"] }
anyhow = { version = "1.0.49" }
Plain futures
If you are using the plain futures crate without runtimes like Tokio, you should
be safe to wrap the asynchronous code in an async block and use the
futures::executor::block_on
to run the future to completion:
use futures::executor::block_on;
async fn hello_world() -> String {
"hello, world!".to_string()
}
fn get() -> String {
block_on(async {
hello_world().await
})
}
fn main() {
println!("{}", get()); // prints "hello, world!"
}
Avoid async
Lastly, you can avoid async code all together by using synchronously/blocking
version of the functions if they are available. In Reqwest, there's a module
called reqwest::blocking
designed specifically for this purpose. So you can
achieve the same thing above without using async.
use anyhow;
use reqwest;
fn get() -> anyhow::Result<String> {
let url = "https://link/to/file/download";
let data = reqwest::blocking::get(url)?.text()?;
Ok(data)
}
It has the following dependencies:
[dependencies]
futures = "0.3"
reqwest = { version = "0.11.6", features = ["blocking"] }
anyhow = { version = "1.0.49" }