我們之前簡單介紹了async/.await的用法,本節將更詳細的介紹async/.await。
async的用法
async主要有兩種用法:async函數和asyn代碼塊(老版本書中說的是三種主要的用法,多了一個async閉包)。這幾種用法都會返回一個Future對象,如下:
async fn foo() -> u8 { 5 }
fn bar() -> impl Future<Output = u8> {
// `Future<Output = u8>`.
async {
let x: u8 = foo().await;
x + 5
}
}
fn baz() -> impl Future<Output = u8> {
// implements `Future<Output = u8>`
let closure = async |x: u8| {
let y: u8 = bar().await;
y + x
};
closure(5)
}
async轉化的Future對象和其它Future一樣是具有惰性的,即在運行之前什麼也不做。運行Future最常見的方式是.await。
async的生命週期
考慮示例:
async fn foo(x: &u8) -> u8 { *x }
根據我們前面的知識,那麼該函數其實等價於如下:
fn foo<'a>(x: &'a u8) -> impl Future<Output = ()> + 'a {
async { *x }
}
這個異步函數返回一個Future對象。如果我們把這個Future對象在線程間進行傳遞,那麼則存在生命週期的問題。如下:
//這種調用就會存在生命週期的問題
fn bad() -> impl Future<Output = ()> {
let x = 5;
foo(&x) // ERROR: `x` does not live long enough
}
正確的調用方式如下:
fn good() -> impl Future<Output = ()> {
async {
let x = 5;
foo(&x).await;
}
}
說明:通過將變量移動到async中,將延長x的生命週期和foo返回的Future生命週期一致。
async move
async 塊和閉包允許 move 關鍵字,就像普通的閉包一樣。一個 async move 塊將獲取它引用變量的所有權,允許它活得比目前的範圍長,但放棄了與其它代碼分享那些變量的能力。
示例如下
- 配置文件:
//Cargo.toml中添加
[dependencies]
futures = "0.3.4
- 源碼:
//src/main.rs
use futures::executor;
async fn move_block() {
let my_string = "foo".to_string();
let f = async move {
println!("{}", my_string);
};
// println!("{}", my_string); //此處不能再使用my_string
f.await
}
fn main() {
executor::block_on(move_block());
}
多線程
在使用多線程Future的excutor時,Future可能在線程之間移動,因此在async主體中使用的任何變量都必須能夠在線程之間傳輸,因爲任何.await變量都可能導致切換到新線程。