Rust中将函数声明为async有什么优势?
Rust Async函数与普通函数的区别及异步声明的收益问题
我知道async函数会返回Future,调用.await会反复执行poll()直到完成,但不清楚它和普通函数的区别。如果一个函数原本就能正常运行,把它声明为async有没有收益?
我有两段实现相同功能的代码,都基于#[tokio::main]运行,两段代码的循环都能并发执行,每秒输出三个数字。
第一段:调用普通函数通过tokio::spawn创建异步任务
use tokio::spawn; use std::time::Duration; pub async fn experiment1() { spawn_task(1); spawn_task(2); loop { println!("3"); tokio::time::sleep(Duration::from_secs(1)).await; } } fn spawn_task(n: i32) { tokio::spawn(async move { loop { println!("{n}"); tokio::time::sleep(Duration::from_secs(1)).await; } }); }
输出:
3 1 2 3 2 1 3 1 2
第二段:使用async和await的版本
use tokio::spawn; use std::time::Duration; pub async fn experiment3() { spawn_task(1).await; spawn_task(2).await; loop { println!("3"); tokio::time::sleep(Duration::from_secs(1)).await; } } async fn spawn_task(n: i32) { tokio::spawn(async move { loop { println!("{n}"); tokio::time::sleep(Duration::from_secs(1)).await; } }); }
输出:
3 1 2 3 1 2 3 2 1
核心分析
1. 普通函数与Async函数的本质差异
- 执行触发逻辑:普通函数调用后立刻执行内部代码;async函数调用后仅返回一个
Future对象,不会立即执行逻辑,只有当这个Future被.await或者交给异步执行器(如tokio::spawn)调度时,才会启动执行。 - 执行权控制:async函数内部可通过
.await主动让出执行权,让同一线程上的其他异步任务获得运行机会;普通函数若不含异步操作,会持续占用线程直到执行结束。
2. 两段代码表现一致的原因
你的第二段代码里的async fn spawn_task其实没有实际的异步等待逻辑:它内部仅同步调用了tokio::spawn(该函数本身是普通函数,提交任务后立即返回),没有任何.await操作。因此这个async函数返回的Future会在被.await时瞬间执行完毕,和调用普通函数的效果完全相同——两段代码都是在启动阶段就把两个循环任务提交给Tokio执行器,自然表现出一样的并发效果。
3. 声明async函数的实际收益场景
只有满足以下情况时,把普通函数改成async才有意义:
- 函数内部需要调用异步API并等待结果:比如要
.await异步IO、定时器等操作,必须将函数声明为async才能使用.await语法。 - 需要将函数逻辑作为Future进行组合调度:比如要把函数逻辑和其他Future用
join!、select!组合,或者交给执行器延迟调度,async函数返回的Future是必要的载体。 - 替换阻塞操作以提升并发效率:如果原函数包含同步阻塞操作(如同步文件读写),改成async并使用异步IO库(如Tokio的异步fs模块),可以避免阻塞线程,让线程能处理更多任务。
如果函数本身不需要等待异步操作,也不需要作为Future被调度,声明为async不仅没有收益,还会增加少量编译后的代码开销。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

