【投稿】刀哥:Rust學習筆記 4

@[TOC](Rust 學習心得<4>:async/await 如何工做)
2019年末 Rust 正式支持 async/await語法,完成了 Rust 協程的最後一塊拼圖,從而異步代碼能夠用一種相似於 Go 的簡潔方式來書寫。然而對於程序員來說,仍是頗有必要理解 async/await 的實現原理。

async

簡單地說, async 語法生成一個實現  Future  對象。以下 async 函數:
   
async fn foo() -> {
...
}
async 關鍵字,將函數的原型修改成返回一個 Future trait object 。而後將執行的結果包裝在一個新的 future 中返回,大體至關於:
   
fn foo() -> impl Future<Output = ()> {
async { ... }
}
更重要的是 async  代碼塊會實現一個匿名的  Future trait object  ,包裹一個  Generator 。也就是一個實現了  Future  的  Generator Generator 其實是一個狀態機,配合 .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 中代碼直到遇到 yield async block 中的 .await 語句在沒法當即完成時會調用 yield 交出控制權等待下一次 resume 。而當全部代碼執行完,也就是狀態機進入 Complete async 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

本文分享自微信公衆號 - Rust語言中文社區(rust-china)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。程序員

相關文章
相關標籤/搜索