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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。程序員