【投稿】刀哥: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
- 如何创建自定义数据pool的RGW实例
- 源码解读提高RGW并发数-fastcgi模式
- ceph-rest-api 用例
- 数据字典项实现方案
- Common Service Locator library
- 源码解读bucket 删除中的一些细节
- 论Spark Streaming的数据可靠性和一致性
- RGW 的GC深入解析与调优
- 大数据查询——HBase读写设计与实践
- 基于Logstash的自动化运维系统实现
- 简谈RGW的index shard计算
- Blackpearl 的 Impersonate
- SparkMLLib中基于DataFrame的TF-IDF
- 运用Python实现WordPress网站大规模自动化发布文章
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- SpringBoot缓存应用实践
- 为什么应该使用 Go module proxy
- 谁能想到,我给技术总监“上了一课”
- 设计模式速览
- 序列模型——吴恩达深度学习课程笔记(五)
- 30分钟学会pyecharts数据可视化
- 用Keras从零开始6步骤训练神经网络
- Keras结构化数据预处理范例——Titanic生存预测
- 从原理上搞懂如何设置线程池参数大小?
- Keras图像数据预处理范例——Cifar2图片分类
- Keras文本数据预处理范例——IMDB影评情感分类
- JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof 使用详解
- Go 垃圾回收
- 如何在SpringBoot中异步请求和异步调用
- 实现 memcached 客户端:TCP、连接池、一致性哈希、自定义协议