【投稿】刀哥: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源创计划”,欢迎正在阅读的你也加入,一块儿分享。程序员

相关文章
相关标签/搜索