Rust async 理解和初步编写
运行环境:Win10 x64, rustc 1.57.0, 作者:乌龙哈里,日期:2021-12-31
一、原始程序
假设我们现在要做个饭,炉子只有一个灶头,步骤一般如下:
1、洗锅、洗米等等,我们设为 fn prepare()
2、开始煮饭。我们设为 fn cooking()
3、洗菜等等,等饭煮好了才可以炒菜。我们设为 fn wash_vegetable()
rust 程序如下:
// #![allow(non_snake_case,unused)] use std::thread::sleep; use std::time::Duration; fn prepare(){ //prepare before cooking rice for i in 1..6 { println!("prepare:{}",i); sleep(Duration::from_millis(500)); } } fn cooking(){ println!("rice cooking..."); for i in (1..=10).rev(){ println!("cooking...{}",i); sleep(Duration::from_millis(500)); } println!("rice cooked!!!"); } fn wash_vegetable(){ for i in 101..106{ println!("vegetable washing:{}",i); sleep(Duration::from_millis(500)); } } fn main(){ prepare(); cooking(); wash_vegetable(); } /*output: prepare:1 prepare:2 prepare:3 prepare:4 prepare:5 rice cooking... cooking...10 cooking...9 cooking...8 cooking...7 cooking...6 cooking...5 cooking...4 cooking...3 cooking...2 cooking...1 rice cooked!!! vegetable washing:101 vegetable washing:102 vegetable washing:103 vegetable washing:104 vegetable washing:105 */
很好的顺序执行。但是饭煮熟需要10个时间片段,这段时间上述例子是白白在等。能不能在饭煮熟的10个时间片段中先来洗洗菜?答案是能,需要并行。
二、线程 thread
并行分线程 thread 和异步 async 两种,我们先用熟悉的线程来操作。
最简单的思路就是把在主程序 main() 中,把 fn coooking() 和 fn wash_vegetable() 这两个函数分别塞进两个线程,然后让线程跑起来就是了。需要用到 std::tread::spawn 这个函数。
首先我们要把 use std::thread::sleep; 改成 use std::thread::{sleep,spawn};,然后就能愉快地去改main() 主函数了。具体改造见如下:
use std::thread::{sleep,spawn}; fn main(){ prepare(); //spawn //英 [spɔːn] //v.产卵;引发;引起;导致;造成 //n.(鱼、蛙等的)卵 let t1=spawn(cooking); let t2=spawn(wash_vegetable); t1.join().unwrap(); t2.join().unwrap(); } /*output: prepare:1 prepare:2 prepare:3 prepare:4 prepare:5 rice cooking... cooking...9 vegetable washing:101 vegetable washing:102 cooking...8 vegetable washing:103 cooking...7 vegetable washing:104 cooking...6 vegetable washing:105 cooking...5 cooking...4 cooking...3 cooking...2 cooking...1 cooking...0 rice cooked!!! */
看见输出了,果然在煮饭的过程中插入洗菜了。
引入线程按下面的套路写,就能跑起来了。
1、let
线程名 = spawn(
函数名 );
2、线程名.join();
其实 spawn()
返回值是 JoinHandle<T>
,先这么理解。
这里要吐槽一下,网上好多教程都是复杂得一塌糊涂,对于我这个非科班的业余人士来说和看天书差不多。还是自己使劲啃,自己动手写才能理解,不需要那么高大上的例子。
线程弄出来了,后面看了异步的种种,都在balabala...说线程很重等等,好吧,我们尝试来学习先进的技术吧。
三、异步 async
查了一下词典,async 应该是 asynchronous 或者是 asynchronization 的简写。 这个 async await 我记得是 C# 先引用的,现在 rust 也有了。好吧,我们开始学习。
rust 最近的版本用的都是标准库 async_std,详细见 Crate async_std 和 async-std。 我的基本理解是:async 和 await 其实是一种标记,需要在同步的函数前标记个 async,调用时末尾标记个.await。和 thread 的调用方式差不多。
首先得改写工程项目的 cargo.toml :
[dependencies] async-std = { version = "1.2.0", features = ["attributes"] }
接着需要在程序头引用 use async_std::task::spawn;
所以下面 main() 主函数需要加入编译条件:#[async_std::main]
。改造具体如下:
// ref:https://docs.rs/async-std/latest/async_std/index.html // cargo.toml // [dependencies] // async-std = { version = "1.2.0", features = ["attributes"] } use std::thread::sleep; use async_std::task::spawn; use std::time::Duration; fn prepare(){ //prepare before cooking rice for i in 1..6 { println!("prepare:{}",i); sleep(Duration::from_millis(500)); } } async fn cooking(){ println!("rice cooking..."); for i in (0..10).rev(){ println!("cooking...{}",i); sleep(Duration::from_millis(500)); } println!("rice cooked!!!"); } async fn wash_vegetable(){ for i in 101..106{ println!("vegetable washing:{}",i); sleep(Duration::from_millis(500)); } } #[async_std::main] async fn main(){ prepare(); let a1=spawn(cooking()); let a2=spawn(wash_vegetable()); a1.await; a2.await; } /*output: prepare:1 prepare:2 prepare:3 prepare:4 prepare:5 rice cooking... cooking...9 vegetable washing:101 cooking...8 vegetable washing:102 cooking...7 vegetable washing:103 cooking...6 vegetable washing:104 cooking...5 vegetable washing:105 cooking...4 cooking...3 cooking...2 cooking...1 cooking...0 rice cooked!!! */
大功告成。终于简单的会用 async 了。
总结一下用途就是,耗时过大的函数需要同步时,在其函数头前标记个 async。调用时在其调用者的 handle 后加个 .await。其他需要同时使用这段时间的函数同样处理。