#IT明星不是梦# 一文读懂Rust的async
一文读懂Rust的async
不同的编程语言表现异步编程的方式可能不一样,Rust跟JavaScript的async/await类似:使用的关键字啊,编程模型啊都差不多啦! 也有些不一样的地方,毕竟Rust是重新设计的语言嘛,比如:在JavaScript中使用Promise表示需要延迟异步执行的计算,在Rust中使用的是Future.在JavaScript中不需要选择指定运行异步代码的运行时,在Rust中需要. Rust还更麻烦了?还得选择指定运行时?
这是因为Rust是可以面向硬件、嵌入式设备的操作系统级别的编程语言就像C++,并且零抽象成本.这就需要Rust需要有选择地把功能包含进标准库里.简单来说,为了满足不同的编程场景Rust标准库就没有包含指定异步代码运行时,我们可以根据具体的场景选择不同的运行时。
是不是感觉还有点晕乎?没关系,下面我们会介绍怎么在Rust中编写异步(async)代码.知道了怎么编写异步代码,也就知道async是什么了.如果你是第一次使用Rust编写异步代码或者第一次使用异步代码库正在迷茫从何入手,那恭喜你,这篇文档特别适合你.开始之前我们先快速的介绍下异步编程的基本要素.
基本要素
编写异步的应用,至少需要俩个crate:
你可能不想在项目中引入过多依赖,但这些依赖就像chrono
和log
是比较基础的依赖.唯一的不同是这些依赖是面向异步编程的.
我们接下来会使用Tokio做为运行时,刚开始你最好也先了解熟悉使用一种运行时,然后再尝试使用其它运行时。
因为这些运行时之间有很多相通
的地方,熟悉了一个再去熟悉其它的就简单了。就像我们学习编程语言一样,学好学深一门编程语言,再去学习其它的语言就快了。不要一开始就几门语言一起学,这样很可能实际开发时这也不行那也不行换来换去还是不能开发出东西.
我们可以像下面这样引入依赖:
[dependencies] futures = { version = "0.3.*" } tokio = {version = "0.2.*", features = ["full"] }
在main.rs
中敲入以下代码:
use futures::prelude::*; use tokio::prelude::*; fn main() { todo!(); }
可以执行下cargo check
如果没什么报错信息,依赖配置就完成了.接下来我们介绍怎么使用运行时。
运行时
像我们先前说的Rust标准库并没有指定异步代码的运行时,所以我们自己选择运行时并配置相应的依赖。这里我们选择了使用Tokio:
tokio = {version = "0.2.*", features = ["full"] }
有些第三方库可能需要我们使用指定的异步代码运行时,因为它们内部是对特定运行时库进行了封装。比如:web开发框架actix_web
就是基于tokio
封装开发的.但大多少情况我们都可以自己选择运行时。无论我们选择那一种运行时,在开始编写代码前都需要先搞清除:
- 怎么
启动
运行时? - 怎么
生成 Future
? - 怎么处理阻塞(IO密集)和CPU密集任务?
搞清除了这三个问题基本上也就学会怎么编写异步代码了.接下来我们就以tokio
为例演示下:
-
启动运行时
可以实例化一个运行时,并派生一个Future指定给运行时。这个Future就是异步代码的主入口,可以把它想象成异步代码的main函数:
async fn app() { todo!() } fn main() { let mut rt = tokio::runtime::Runtime::new().unwrap(); let future = app(); rt.block_on(future); }
还可以使用宏,简化代码为:
#[tokio::main] async fn main() { }
虽然代码行数少了,功能跟上面的代码还是一样的哦!
-
为运行时生成Future
你想并发运行多个任务时,就可以像这样生成Future:
use tokio::task; async fn our_async_program() { todo!(); } async fn app() { let concurrent_future = task::spawn(our_async_program()); todo!() }
-
处理阻塞和CPU密集性任务
什么是阻塞性的任务?什么是CPU密集性的任务呢?可以简单的理解为这两种任务都会长时间的霸占CPU阻塞线程继续执行其它任务.就好比工地上有个包工头专门负责分配任务给小工门干,有些小活小任务包工头可能顺手就干了,但是一些耗时比较长的比如去搬一车砖头,包工头就不能自己去干了,因为它去搬砖头了就没人负责任务分配了,小工们活都干完了只能等着包工头分配任务才能继续干活.包工头呢?还在搬砖头呢.显然这是会影响整体工作效率的。代码也一样,要有个专门负责总体分配任务的线程,在这个线程中就不能再执行其它比较耗费时间的的任务了。那耗费时间的任务谁来执行呢?小工呗,也就是派生新的Future. 就像这个样子:
use tokio::task; fn fib_cpu_intensive(n: u32) -> u32 { match n { 0 => 0, 1 => 1, n => fib_cpu_intensive(n - 1) + fib_cpu_intensive(n - 2), } } async fn app() { let threadpool_future = task::spawn_blocking(||fib_cpu_intensive(30)); todo!() }
tokio是使用的spawn_blocking去派生新的Future使用新的线程执行比较耗时的任务,其它运行时库可能API不一样但也会提供类似的方法.
异步开发样例
支持我们已经学习了怎么使用Rust编写异步代码,接下来把所学内容整合到一起做个样例:
use futures::prelude::*; use tokio::prelude::*; use tokio::task; use log::*; // Just a generic Result type to ease error handling for us. Errors in multithreaded // async contexts needs some extra restrictions type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; async fn app() -> Result<()> { // I treat this as the `main` function of the async part of our program. todo!() } fn main() { env_logger::init(); let mut rt = tokio::runtime::Runtime::new().unwrap(); match rt.block_on(app()) { Ok(_) => info!("Done"), Err(e) => error!("An error ocurred: {}", e), }; }
使用到的crate有:
- 提供异步代码运行时的
tokio
- Rust日志门面
log
- 日志工具
env_logger
Cargo.toml
类似这个样子:
[dependencies] futures = { version = "0.3.*"} tokio = {version = "0.2.*", features = ["full"] } log = "0.4.*" env_logger = "0.7.*"
需要注意的是
env_logger
需要根据环境变量RUST_LOG
设置日志级别
基本上所有的异步编程项目都可以使用类似这样的依赖配置和main.rs
.根据不同的使用场景还可以优化下错误处理和日志.比如可以考虑使用 Anyhow处理错误,可以考虑使用 async-log更好的在异步多线程环境中输出日志.在本文档中接下来的代码就基于这个样例模板开发了。
异步函数
在Rust中编写异步函数跟先前编写普通函数有点不一样.先前接触Rust函数时,你可能已经注意到函数的参数返回值都需要声明确切的类型。异步函数的返回值都是经过Future
包装的。如果你读了关于Future
的文档,按照这个思路你可能认为应该像下面这样编写异步函数:
async fn our_async_program() -> impl Future<Output = Result<String>> { future::ok("Hello world".to_string()).await }
不用这么麻烦,比较Rust是重新设计的语言.当你使用async关键字时,Rust会自动的使用Future封装返回只,所以你原来怎么给普通函数定义返回值就继续那么地干,就像这个样子:
async fn our_async_program() -> Result<String> { future::ok("Hello world".to_string()).await }
这里使用的
future::ok
是future库提供的方便我们开发使用的,用于生成状态为ready
的future
.
你可能会见到使用异步代码块async {...}
创建异步代码的,这是为了更灵活的定义返回值类型,不过大多少情况下使用异步函数就够了.接下来我们编写一个使用异步函数的例子.
创建Web请求
在Rust中future
是lazy
(懒)的.也就是说,默认情况下当你创建了一个future,它是什么都不干的,非得等你调用await
告诉它该干活了,它才开始干活.
接下来我们以发起处理Web请求的场景用代码演示一下子:
fn slowwly(delay_ms: u32) -> reqwest::Url { let url = format!( "http://slowwly.robertomurray.co.uk/delay/{}/url/http://www.google.co.uk", delay_ms, ); reqwest::Url::parse(&url).unwrap() } async fn app() -> Result<()> { info!("Starting program!"); let _resp1 = reqwest::get(slowwly(1000)).await?; info!("Got response 1"); let _resp2 = reqwest::get(slowwly(1000)).await?; info!("Got response 2"); Ok(()) }
创建web请求使用到了reqwest库,需要把这个库添加到Cargo.toml的依赖区域:
reqwest = "0.10.*"
执行上面的代码输出类似这个样子:
1.264 [INFO] - Got response 1 2.467 [INFO] - Got response 2 2.468 [INFO] - Done
这里的日志格式是自定义的,前面的数字是程序执行的时间,自定义日志格式的代码是这个样子地:
et start = std::time::Instant::now(); env_logger::Builder::from_default_env().format(move |buf, rec| { let t = start.elapsed().as_secs_f32(); writeln!(buf, "{:.03} [{}] - {}", t, rec.level(),rec.args()) }).init();
从日志输出可以看出,我们的函数并不是一起执行的,而是一个执行完成后另一个才开始执行的,因为我们这里还是使用的普通函数并没有使用异步函数.接下来是修改为异步函数的版本:
async fn request(n: usize) -> Result<()> { reqwest::get(slowwly(1000)).await?; info!("Got response {}", n); Ok(()) } async fn app() -> Result<()> { let resp1 = task::spawn(request(1)); let resp2 = task::spawn(request(2)); let _ = resp1.await??; let _ = resp2.await??; Ok(()) }
tokio提供的spawn函数可以让我们使用多线程并发执行异步函数.
执行的效果类似这个样子:
1.247 [INFO] - Got response 2 1.256 [INFO] - Got response 1 1.257 [INFO] - Done
可以跟上面使用普通函数的方式对比一下子,是不是总体效率快多了,俩个请求不需要互相等待,各自说干就干,就是这么快.
补充
什么时候该派生Future执行任务呢?这里有几条建议
- 优先选用没有阻塞的操作库.
- 如果不确定就派生一个吧.
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
戴尔重磅发布多款存储新品,以端到端存储产品为“新基建”赋能
【51CTO.com原创稿件】2020年7月10日,主题为“奔涌之势,智造未来”的戴尔科技集团2020新品发布会通过线上直播方式如期召开。会上,戴尔发布了Power家族系列新品和解决方案,涵盖了存储、服务器、超融合、云计算等领域。记者注意到,在今年5月份戴尔发布“第五代存储”核心产品PowerStore之后,本次发布会上戴尔再度发力,推出了PowerScale、PowerFlex和PowerProtect存储和数据保护新产品,进一步完善了戴尔存储产品线,真正为企业提供了端到端的存储产品和解决方案。 端到端存储产品推动企业数字化转型 疫情让企业看到了数字化转型的重要性,加速推动了企业数字化转型的进程。而数字化转型与数据、存储密切相关。 众所周知,企业数字化转型的核心是充分利用数据、挖掘数据价值,给企业经营提供决策。随着5G、物联网、云计算等技术的发展,企业获取数据的方式变得越来越多样,数据呈现出爆炸式增长的态势,数据类型也发生了巨大变化,非结构化数据成为企业数据的主要组成部分。 为此,本次新品发布会上戴尔推出了Dell EMC PowerScale非结构化数据存储,其集成了业内领先的存储...
- 下一篇
picbed 1.7.0 发布,基于 Flask 的 Web 自建图床
picbed是一款简洁不小气的web图床,基于Python Flask,最近版本更新了一些功能。 更新如下: 功能: 集成文档 LinkToken统计中增加解析UserAgent相关字段 升级助手:通过命令行完成升级所需要的数据迁移、字段变更等 增加用户状态字段,实现注册用户审核与审核开关 允许审核用户留言 控制台设置、取消某用户为管理员 用户资料增加邮箱,并支持验证(邮件发送钩子、模板) 钩子管理器调用钩子方法增加_mode、_every 修复: 上一页地址从注册到登录页面的问题 更改: 全局设置中站点后缀改为站点名称 钩子管理器调用钩子方法的args、kwargs参数改为_args、_kwargs 优化: 引用轻量图标字体库,全站增设图标 用户脚本设置LinkToken改为渲染下拉表以供选择 用户脚本上传字段自动更随全局配置 登录与上传接口,增加最近一次登录时间 钩子管理器调用钩子方法返回执行结果
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Red5直播服务器,属于Java语言的直播服务器
- CentOS关闭SELinux安全模块
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS8编译安装MySQL8.0.19