自从接触Go后对异步编程(同步方式实现异步功能)比较感兴趣。Google后得知Rust也在大力支持异步编程,Rust中主流的异步编程库有:tokio 和 async-std。大致浏览文档后,发现两者的语法是很相似的。tokio很早就支持异步编程的特性,而且一直在迭代,也有大量的框架使用tokio。async-std1.0版本发布于2019年9月26日,可以说是非常年轻。查阅一些资料后,选择async-std库作为Rust异步编程的主要学习库。原因也很简单:
- 声称兼容标准库,
tokio后续的版本也往这方面靠 - 没有
tokio的历史包袱,可以更快的迭代新功能 - 比
tokio库更加小巧,功能也足够强大
下面看一个官方例子:
extern crate async_std;
use async_std::{fs::File, io, io::prelude::*, task};
async fn read_file(path: &str) -> io::Result<String> {
let mut file = File::open(path).await?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
Ok(contents)
}
fn main() {
let reader_task = task::spawn(async {
let result = read_file("./src/main.rs").await;
match result {
Ok(s) => println!("{}", s),
Err(e) => println!("Error reading file: {:?}", e)
}
});
task::block_on(reader_task);
}
这个例子主要的功能是:读取文件内容并输出到标准输出。异步体现在:
- 打开文件如果被阻塞,会主动让出线程。
File::open(path).await? - 读取文件内容,阻塞也会主动让出线程。
file.read_to_string(&mut contents).await?
Rust异步编程的核心关键字包括:
- async:构建一个
Future的结构 - .await:通过等待
Future返回
下面看一下Future:
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>;
...
}
核心函数是poll,主要作用是检查Future是否能够返回。此函数不应该包含阻塞的函数调用,而是检查task的状态(task是需要执行的代码块或函数)。函数的返回值:
- Poll::Pending:
future没有准备好 - Poll::Ready(val):
future准备好,val值则是task的返回值。
如果future没有准备好,则需要将task再次放进运行时系统进行调度。通过Context变量的walker函数进行。
pub struct Context<'a> {
waker: &'a Waker,
...
}
impl<'a> Context<'a> {
pub fn waker(&self) -> &'a Waker {
&self.waker
}
...
}
整个async-std异步调用的过程大概如下:
- 通过
async关键子构建future结构 - 通过
.await或task::spawn等方法,将future结构关联的函数或代码块包装成task放入到运行时系统进行调度 - 等待事件触发。如:套接字读写、文件读写、定时器、信号、锁等
- 调度事件关联的
task,并对future结构的返回值进行检查。如果是Ready,则返回结果,原来的执行流继续进行;否则,调用Context的waker函数将task再次放入运行时系统进行调度,等待下一次事件触发。