当前位置 : 主页 > 编程语言 > java >

【投稿】刀哥:Rust学习笔记 4

来源:互联网 收集:自由互联 发布时间:2022-06-30
@[TOC](Rust 学习心得<4>:async/await 如何工作) 2019年底 ​​ Rust ​​ 正式支持 async/await语法,完成了 ​​ Rust ​​ 协程的最后一块拼图,从而异步代码可以用一种类似于 ​​ Go ​​

@[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日报】2020-08-24 理解 Rust 的切片
下一篇:没有了
网友评论