async/await 如何工做 | Rust學習筆記

做者:謝敬偉,江湖人稱「刀哥」,20年IT老兵,數據通訊網絡專家,電信網絡架構師,目前任Netwarps開發總監。刀哥在操做系統、網絡編程、高併發、高吞吐、高可用性等領域有多年的實踐經驗,並對網絡及編程等方面的新技術有濃厚的興趣。html


2019年末Rust正式支持 async/await語法,完成了Rust協程的最後一塊拼圖,從而異步代碼能夠用一種相似於Go的簡潔方式來書寫。然而對於程序員來說,仍是頗有必要理解async/await的實現原理。git

async

簡單地說,async語法生成一個實現 Future 對象。以下async函數:程序員

async fn foo() -> {
    ...
}

async關鍵字,將函數的原型修改成返回一個Future trait object。而後將執行的結果包裝在一個新的future中返回,大體至關於:github

fn foo() -> impl Future<Output = ()> {
    async { ... }
}

更重要的是async 代碼塊會實現一個匿名的 Future trait object ,包裹一個 Generator。也就是一個實現了 FutureGeneratorGenerator其實是一個狀態機,配合.await當每次async 代碼塊中任何返回 Poll::Pending則即調用generator yeild,讓出執行權,一旦恢復執行,generator resume 繼續執行剩餘流程。編程

如下是這個狀態機Future的代碼:安全

pub const fn from_generator<T>(gen: T) -> impl Future<Output = T::Return>
where
    T: Generator<ResumeTy, Yield = ()>,
{
    struct GenFuture<T: Generator<ResumeTy, Yield = ()>>(T);

    impl<T: Generator<ResumeTy, Yield = ()>> !Unpin for GenFuture<T> {}

    impl<T: Generator<ResumeTy, Yield = ()>> Future for GenFuture<T> {
        type Output = T::Return;
        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            let gen = unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) };

            match gen.resume(ResumeTy(NonNull::from(cx).cast::<Context<'static>>())) {
                GeneratorState::Yielded(()) => Poll::Pending,  // 當代碼沒法繼續執行,讓出控制權,返回 Pending,等待喚醒
                GeneratorState::Complete(x) => Poll::Ready(x), // 執行完畢
            }
        }
    }
    GenFuture(gen)
}

能夠看到這個特別的Future是經過Generator來運行的。每一次gen.resume()會順序執行async block中代碼直到遇到yieldasync block中的.await語句在沒法當即完成時會調用yield交出控制權等待下一次resume。而當全部代碼執行完,也就是狀態機進入Completeasync block返回Poll::Ready,表明Future執行完畢。微信

await

每個await自己就像一個執行器,在循環中查詢Future的狀態。若是返回Pending,則 yield,不然退出循環,結束當前Future網絡

代碼邏輯大體以下:架構

loop {
    match some_future.poll() {
        Pending => yield,
        Ready(x) => break
    }
}

爲了更好地理解async/await的原理,咱們來看一個簡單例子:併發

async fn foo() {
    do_something_1();
    some_future.await;
    do_something_2();
}

使用async修飾的異步函數foo被改寫爲一個Generator狀態機驅動的Future,其內部有一個some_future.await,中間穿插do_something_x()等其餘操做。當執行foo().await時,首先完成do_something_1(),而後執行some_future.await,若some_future返回Pending,這個Pending被轉換爲yield,所以頂層foo()暫時也返回Pending,待下次喚醒後,foo()調用resume()繼續輪詢some_future,若some_future返回Ready,表示some_future.await完畢,則foo()開始執行do_something_2()

這裏的關鍵點在於,由於狀態機的控制,因此當foo()再次被喚醒時,不會重複執行do_something_1(),而是會從上次yield的的地方繼續執行some_future.await,至關於完成了一次任務切換,這也是無棧協程的工做方式。

總結

async/await 經過一個狀態機來控制代碼的流程,配合Executor完成協程的切換。在此以後,書寫異步代碼不須要手動寫Future及其poll方法,特別是異步邏輯的狀態機也是由async自動生成,大大簡化程序員的工做。雖然async/await出現的時間不長,目前純粹使用async/await書寫的代碼還不是主流,但能夠樂觀地期待,從此更多的項目會使用這個新語法。

參考
Futures Explained in 200 Lines of Rust


深圳星鏈網科科技有限公司(Netwarps),專一於互聯網安全存儲領域技術的研發與應用,是先進的安全存儲基礎設施提供商,主要產品有去中心化文件系統(DFS)、企業聯盟鏈平臺(EAC)、區塊鏈操做系統(BOS)。微信公衆號:Netwarps

相關文章
相關標籤/搜索