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。也就是一个实现了 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

作者:谢敬伟,江湖人称“刀哥”,20年IT老兵,数据通信网络专家,电信网络架构师,目前任Netwarps开发总监。刀哥在操作系统、网络编程、高并发、高吞吐、高可用性等畛域有多年的实践经验,并对网络及编程等方面的新技术有浓重的趣味。

深圳星链网科科技有限公司(Netwarps),专一于互联网安全存储畛域技术的研发与利用,是先进的平安存储基础设施提供商,次要产品有去中心化文件系统(DFS)、企业联盟链平台(EAC)、区块链操作系统(BOS)。
微信公众号:Netwarps