004 Rust 非同步程式設計,async await 的詳細用法

linghuyichong發表於2020-06-14

我們之前簡單介紹了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變數都可能導致切換到新執行緒。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
令狐一衝

相關文章